Merge pull request #563 from biconou/squash
Introduction of a new kind of jukebox player based on the javasound api.master
commit
f5454bb0fd
@ -0,0 +1,356 @@ |
||||
package org.airsonic.player.service; |
||||
|
||||
import com.github.biconou.AudioPlayer.JavaPlayer; |
||||
import com.github.biconou.AudioPlayer.api.*; |
||||
import org.airsonic.player.domain.*; |
||||
import org.airsonic.player.domain.Player; |
||||
import org.airsonic.player.util.FileUtil; |
||||
import org.apache.commons.lang.StringUtils; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Hashtable; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
|
||||
/** |
||||
* @author R??mi Cocula |
||||
*/ |
||||
@Service |
||||
public class JukeboxJavaService { |
||||
|
||||
private static final org.slf4j.Logger log = LoggerFactory.getLogger(JukeboxJavaService.class); |
||||
|
||||
@Autowired |
||||
private AudioScrobblerService audioScrobblerService; |
||||
@Autowired |
||||
private StatusService statusService; |
||||
@Autowired |
||||
private SettingsService settingsService; |
||||
@Autowired |
||||
private SecurityService securityService; |
||||
@Autowired |
||||
private MediaFileService mediaFileService; |
||||
|
||||
|
||||
private TransferStatus status; |
||||
private Map<String, com.github.biconou.AudioPlayer.api.Player> activeAudioPlayers = new Hashtable<>(); |
||||
private Map<String, List<com.github.biconou.AudioPlayer.api.Player>> activeAudioPlayersPerMixer = new Hashtable<>(); |
||||
private final static String DEFAULT_MIXER_ENTRY_KEY = "_default"; |
||||
|
||||
/** |
||||
* Finds the corresponding active audio player for a given airsonic player. |
||||
* The JukeboxJavaService references all active audio players in a map indexed by airsonic player id. |
||||
* |
||||
* @param airsonicPlayer a given airsonic player. |
||||
* @return the corresponding active audio player of null if none exists. |
||||
*/ |
||||
private com.github.biconou.AudioPlayer.api.Player retrieveAudioPlayerForAirsonicPlayer(Player airsonicPlayer) { |
||||
com.github.biconou.AudioPlayer.api.Player foundPlayer = activeAudioPlayers.get(airsonicPlayer.getId()); |
||||
if (foundPlayer == null) { |
||||
synchronized (activeAudioPlayers) { |
||||
foundPlayer = initAudioPlayer(airsonicPlayer); |
||||
if (foundPlayer == null) { |
||||
throw new RuntimeException("Did not initialized a player"); |
||||
} else { |
||||
activeAudioPlayers.put(airsonicPlayer.getId(), foundPlayer); |
||||
String mixer = airsonicPlayer.getJavaJukeboxMixer(); |
||||
if (StringUtils.isBlank(mixer)) { |
||||
mixer = DEFAULT_MIXER_ENTRY_KEY; |
||||
} |
||||
List<com.github.biconou.AudioPlayer.api.Player> playersForMixer = activeAudioPlayersPerMixer.get(mixer); |
||||
if (playersForMixer == null) { |
||||
playersForMixer = new ArrayList<>(); |
||||
activeAudioPlayersPerMixer.put(mixer, playersForMixer); |
||||
} |
||||
playersForMixer.add(foundPlayer); |
||||
} |
||||
} |
||||
} |
||||
return foundPlayer; |
||||
} |
||||
|
||||
|
||||
private com.github.biconou.AudioPlayer.api.Player initAudioPlayer(final Player airsonicPlayer) { |
||||
|
||||
if (!airsonicPlayer.getTechnology().equals(PlayerTechnology.JAVA_JUKEBOX)) { |
||||
throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player"); |
||||
} |
||||
|
||||
log.info("begin initAudioPlayer"); |
||||
|
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer; |
||||
|
||||
if (StringUtils.isNotBlank(airsonicPlayer.getJavaJukeboxMixer())) { |
||||
log.info("use mixer : {}", airsonicPlayer.getJavaJukeboxMixer()); |
||||
audioPlayer = new JavaPlayer(airsonicPlayer.getJavaJukeboxMixer()); |
||||
} else { |
||||
log.info("use default mixer"); |
||||
audioPlayer = new JavaPlayer(); |
||||
} |
||||
if (audioPlayer != null) { |
||||
audioPlayer.registerListener(new PlayerListener() { |
||||
@Override |
||||
public void onBegin(int index, File currentFile) { |
||||
onSongStart(airsonicPlayer); |
||||
} |
||||
|
||||
@Override |
||||
public void onEnd(int index, File file) { |
||||
onSongEnd(airsonicPlayer); |
||||
} |
||||
|
||||
@Override |
||||
public void onFinished() { |
||||
airsonicPlayer.getPlayQueue().setStatus(PlayQueue.Status.STOPPED); |
||||
} |
||||
|
||||
@Override |
||||
public void onStop() { |
||||
airsonicPlayer.getPlayQueue().setStatus(PlayQueue.Status.STOPPED); |
||||
} |
||||
|
||||
@Override |
||||
public void onPause() { |
||||
// Nothing to do here
|
||||
} |
||||
|
||||
}); |
||||
log.info("New audio player {} has been initialized.", audioPlayer.toString()); |
||||
} else { |
||||
throw new RuntimeException("AudioPlayer has not been initialized properly"); |
||||
} |
||||
return audioPlayer; |
||||
} |
||||
|
||||
|
||||
public int getPosition(final Player airsonicPlayer) { |
||||
|
||||
if (!airsonicPlayer.getTechnology().equals(PlayerTechnology.JAVA_JUKEBOX)) { |
||||
throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player"); |
||||
} |
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
if (audioPlayer == null) { |
||||
return 0; |
||||
} else { |
||||
return audioPlayer.getPlayingInfos().currentAudioPositionInSeconds(); |
||||
} |
||||
} |
||||
|
||||
public void setPosition(final Player airsonicPlayer, int positionInSeconds) { |
||||
if (!airsonicPlayer.getTechnology().equals(PlayerTechnology.JAVA_JUKEBOX)) { |
||||
throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player"); |
||||
} |
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
if (audioPlayer != null) { |
||||
audioPlayer.setPos(positionInSeconds); |
||||
} else { |
||||
throw new RuntimeException("The player " + airsonicPlayer.getName() + " has no real audio player"); |
||||
} |
||||
} |
||||
|
||||
public float getGain(final Player airsonicPlayer) { |
||||
if (!airsonicPlayer.getTechnology().equals(PlayerTechnology.JAVA_JUKEBOX)) { |
||||
throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player"); |
||||
} |
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
if (audioPlayer != null) { |
||||
return audioPlayer.getGain(); |
||||
} |
||||
return 0.5f; |
||||
} |
||||
|
||||
public void setGain(final Player airsonicPlayer, final float gain) { |
||||
if (!airsonicPlayer.getTechnology().equals(PlayerTechnology.JAVA_JUKEBOX)) { |
||||
throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player"); |
||||
} |
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
log.debug("setGain : gain={}", gain); |
||||
if (audioPlayer != null) { |
||||
audioPlayer.setGain(gain); |
||||
} else { |
||||
throw new RuntimeException("The player " + airsonicPlayer.getName() + " has no real audio player"); |
||||
} |
||||
} |
||||
|
||||
|
||||
private void onSongStart(Player player) { |
||||
MediaFile file = player.getPlayQueue().getCurrentFile(); |
||||
log.info("[onSongStart] {} starting jukebox for \"{}\"", player.getUsername(), FileUtil.getShortPath(file.getFile())); |
||||
if (status != null) { |
||||
statusService.removeStreamStatus(status); |
||||
status = null; |
||||
} |
||||
status = statusService.createStreamStatus(player); |
||||
status.setFile(file.getFile()); |
||||
status.addBytesTransfered(file.getFileSize()); |
||||
mediaFileService.incrementPlayCount(file); |
||||
scrobble(player, file, false); |
||||
} |
||||
|
||||
private void onSongEnd(Player player) { |
||||
MediaFile file = player.getPlayQueue().getCurrentFile(); |
||||
log.info("[onSongEnd] {} stopping jukebox for \"{}\"", player.getUsername(), FileUtil.getShortPath(file.getFile())); |
||||
if (status != null) { |
||||
statusService.removeStreamStatus(status); |
||||
status = null; |
||||
} |
||||
scrobble(player, file, true); |
||||
} |
||||
|
||||
private void scrobble(Player player, MediaFile file, boolean submission) { |
||||
if (player.getClientId() == null) { // Don't scrobble REST players.
|
||||
audioScrobblerService.register(file, player.getUsername(), submission, null); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Plays the playqueue of a jukebox player starting at the beginning. |
||||
* |
||||
* @param airsonicPlayer |
||||
*/ |
||||
public void play(Player airsonicPlayer) { |
||||
log.debug("begin play jukebox : player = id:{};name:{}", airsonicPlayer.getId(), airsonicPlayer.getName()); |
||||
|
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
|
||||
// Control user authorizations
|
||||
User user = securityService.getUserByName(airsonicPlayer.getUsername()); |
||||
if (!user.isJukeboxRole()) { |
||||
log.warn("{} is not authorized for jukebox playback.", user.getUsername()); |
||||
return; |
||||
} |
||||
|
||||
log.debug("Different file to play -> start a new play list"); |
||||
if (airsonicPlayer.getPlayQueue().getCurrentFile() != null) { |
||||
audioPlayer.setPlayList(new PlayList() { |
||||
|
||||
@Override |
||||
public File getNextAudioFile() throws IOException { |
||||
airsonicPlayer.getPlayQueue().next(); |
||||
return getCurrentAudioFile(); |
||||
} |
||||
|
||||
@Override |
||||
public File getCurrentAudioFile() { |
||||
MediaFile current = airsonicPlayer.getPlayQueue().getCurrentFile(); |
||||
if (current != null) { |
||||
return airsonicPlayer.getPlayQueue().getCurrentFile().getFile(); |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getSize() { |
||||
return airsonicPlayer.getPlayQueue().size(); |
||||
} |
||||
|
||||
@Override |
||||
public int getIndex() { |
||||
return airsonicPlayer.getPlayQueue().getIndex(); |
||||
} |
||||
}); |
||||
// Close any other player using the same mixer.
|
||||
String mixer = airsonicPlayer.getJavaJukeboxMixer(); |
||||
if (StringUtils.isBlank(mixer)) { |
||||
mixer = DEFAULT_MIXER_ENTRY_KEY; |
||||
} |
||||
List<com.github.biconou.AudioPlayer.api.Player> playersForSameMixer = activeAudioPlayersPerMixer.get(mixer); |
||||
playersForSameMixer.forEach(player -> { |
||||
if (player != audioPlayer) { |
||||
player.close(); |
||||
} |
||||
}); |
||||
audioPlayer.play(); |
||||
} |
||||
} |
||||
|
||||
public void start(Player airsonicPlayer) throws Exception { |
||||
log.debug("begin start jukebox : player = id:{};name:{}", airsonicPlayer.getId(), airsonicPlayer.getName()); |
||||
|
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
|
||||
// Control user authorizations
|
||||
User user = securityService.getUserByName(airsonicPlayer.getUsername()); |
||||
if (!user.isJukeboxRole()) { |
||||
log.warn("{} is not authorized for jukebox playback.", user.getUsername()); |
||||
return; |
||||
} |
||||
|
||||
log.debug("PlayQueue.Status is {}", airsonicPlayer.getPlayQueue().getStatus()); |
||||
audioPlayer.play(); |
||||
} |
||||
|
||||
public void stop(Player airsonicPlayer) throws Exception { |
||||
log.debug("begin stop jukebox : player = id:{};name:{}", airsonicPlayer.getId(), airsonicPlayer.getName()); |
||||
|
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
|
||||
// Control user authorizations
|
||||
User user = securityService.getUserByName(airsonicPlayer.getUsername()); |
||||
if (!user.isJukeboxRole()) { |
||||
log.warn("{} is not authorized for jukebox playback.", user.getUsername()); |
||||
return; |
||||
} |
||||
|
||||
log.debug("PlayQueue.Status is {}", airsonicPlayer.getPlayQueue().getStatus()); |
||||
audioPlayer.pause(); |
||||
} |
||||
|
||||
/** |
||||
* @param airsonicPlayer |
||||
* @param index |
||||
* @throws Exception |
||||
*/ |
||||
public void skip(Player airsonicPlayer, int index, int offset) throws Exception { |
||||
log.debug("begin skip jukebox : player = id:{};name:{}", airsonicPlayer.getId(), airsonicPlayer.getName()); |
||||
|
||||
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); |
||||
|
||||
// Control user authorizations
|
||||
User user = securityService.getUserByName(airsonicPlayer.getUsername()); |
||||
if (!user.isJukeboxRole()) { |
||||
log.warn("{} is not authorized for jukebox playback.", user.getUsername()); |
||||
return; |
||||
} |
||||
|
||||
if (index == 0 && offset == 0) { |
||||
play(airsonicPlayer); |
||||
} else { |
||||
if (offset == 0) { |
||||
audioPlayer.stop(); |
||||
audioPlayer.play(); |
||||
} else { |
||||
audioPlayer.setPos(offset); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) { |
||||
this.audioScrobblerService = audioScrobblerService; |
||||
} |
||||
|
||||
public void setStatusService(StatusService statusService) { |
||||
this.statusService = statusService; |
||||
} |
||||
|
||||
public void setSettingsService(SettingsService settingsService) { |
||||
this.settingsService = settingsService; |
||||
} |
||||
|
||||
public void setSecurityService(SecurityService securityService) { |
||||
this.securityService = securityService; |
||||
} |
||||
|
||||
public void setMediaFileService(MediaFileService mediaFileService) { |
||||
this.mediaFileService = mediaFileService; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,210 @@ |
||||
/* |
||||
This file is part of Airsonic. |
||||
|
||||
Airsonic is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
Airsonic is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with Airsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2016 (C) Airsonic Authors |
||||
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus |
||||
*/ |
||||
package org.airsonic.player.service; |
||||
|
||||
import org.airsonic.player.domain.*; |
||||
import org.airsonic.player.service.jukebox.AudioPlayer; |
||||
import org.airsonic.player.util.FileUtil; |
||||
import org.apache.commons.io.IOUtils; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.io.InputStream; |
||||
|
||||
/** |
||||
* Plays music on the local audio device. |
||||
* |
||||
* @author Sindre Mehus |
||||
*/ |
||||
@Service |
||||
public class JukeboxLegacySubsonicService implements AudioPlayer.Listener { |
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JukeboxLegacySubsonicService.class); |
||||
|
||||
@Autowired |
||||
private TranscodingService transcodingService; |
||||
@Autowired |
||||
private AudioScrobblerService audioScrobblerService; |
||||
@Autowired |
||||
private StatusService statusService; |
||||
@Autowired |
||||
private SettingsService settingsService; |
||||
@Autowired |
||||
private SecurityService securityService; |
||||
@Autowired |
||||
private MediaFileService mediaFileService; |
||||
|
||||
private AudioPlayer audioPlayer; |
||||
private Player player; |
||||
private TransferStatus status; |
||||
private MediaFile currentPlayingFile; |
||||
private float gain = AudioPlayer.DEFAULT_GAIN; |
||||
private int offset; |
||||
|
||||
/** |
||||
* Updates the jukebox by starting or pausing playback on the local audio device. |
||||
* |
||||
* @param player The player in question. |
||||
* @param offset Start playing after this many seconds into the track. |
||||
*/ |
||||
public synchronized void updateJukebox(Player player, int offset) throws Exception { |
||||
User user = securityService.getUserByName(player.getUsername()); |
||||
if (!user.isJukeboxRole()) { |
||||
LOG.warn(user.getUsername() + " is not authorized for jukebox playback."); |
||||
return; |
||||
} |
||||
|
||||
if (player.getPlayQueue().getStatus() == PlayQueue.Status.PLAYING) { |
||||
this.player = player; |
||||
MediaFile result; |
||||
synchronized (player.getPlayQueue()) { |
||||
result = player.getPlayQueue().getCurrentFile(); |
||||
} |
||||
play(result, offset); |
||||
} else { |
||||
if (audioPlayer != null) { |
||||
audioPlayer.pause(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private synchronized void play(MediaFile file, int offset) { |
||||
InputStream in = null; |
||||
try { |
||||
|
||||
// Resume if possible.
|
||||
boolean sameFile = file != null && file.equals(currentPlayingFile); |
||||
boolean paused = audioPlayer != null && audioPlayer.getState() == AudioPlayer.State.PAUSED; |
||||
if (sameFile && paused && offset == 0) { |
||||
audioPlayer.play(); |
||||
} else { |
||||
this.offset = offset; |
||||
if (audioPlayer != null) { |
||||
audioPlayer.close(); |
||||
if (currentPlayingFile != null) { |
||||
onSongEnd(currentPlayingFile); |
||||
} |
||||
} |
||||
|
||||
if (file != null) { |
||||
int duration = file.getDurationSeconds() == null ? 0 : file.getDurationSeconds() - offset; |
||||
TranscodingService.Parameters parameters = new TranscodingService.Parameters(file, new VideoTranscodingSettings(0, 0, offset, duration, false)); |
||||
String command = settingsService.getJukeboxCommand(); |
||||
parameters.setTranscoding(new Transcoding(null, null, null, null, command, null, null, false)); |
||||
in = transcodingService.getTranscodedInputStream(parameters); |
||||
audioPlayer = new AudioPlayer(in, this); |
||||
audioPlayer.setGain(gain); |
||||
audioPlayer.play(); |
||||
onSongStart(file); |
||||
} |
||||
} |
||||
|
||||
currentPlayingFile = file; |
||||
|
||||
} catch (Exception x) { |
||||
LOG.error("Error in jukebox: " + x, x); |
||||
IOUtils.closeQuietly(in); |
||||
} |
||||
} |
||||
|
||||
public synchronized void stateChanged(AudioPlayer audioPlayer, AudioPlayer.State state) { |
||||
if (state == AudioPlayer.State.EOM) { |
||||
player.getPlayQueue().next(); |
||||
MediaFile result; |
||||
synchronized (player.getPlayQueue()) { |
||||
result = player.getPlayQueue().getCurrentFile(); |
||||
} |
||||
play(result, 0); |
||||
} |
||||
} |
||||
|
||||
public synchronized float getGain() { |
||||
return gain; |
||||
} |
||||
|
||||
public synchronized int getPosition() { |
||||
return audioPlayer == null ? 0 : offset + audioPlayer.getPosition(); |
||||
} |
||||
|
||||
/** |
||||
* Returns the player which currently uses the jukebox. |
||||
* |
||||
* @return The player, may be {@code null}. |
||||
*/ |
||||
public Player getPlayer() { |
||||
return player; |
||||
} |
||||
|
||||
private void onSongStart(MediaFile file) { |
||||
LOG.info(player.getUsername() + " starting jukebox for \"" + FileUtil.getShortPath(file.getFile()) + "\""); |
||||
status = statusService.createStreamStatus(player); |
||||
status.setFile(file.getFile()); |
||||
status.addBytesTransfered(file.getFileSize()); |
||||
mediaFileService.incrementPlayCount(file); |
||||
scrobble(file, false); |
||||
} |
||||
|
||||
private void onSongEnd(MediaFile file) { |
||||
LOG.info(player.getUsername() + " stopping jukebox for \"" + FileUtil.getShortPath(file.getFile()) + "\""); |
||||
if (status != null) { |
||||
statusService.removeStreamStatus(status); |
||||
} |
||||
scrobble(file, true); |
||||
} |
||||
|
||||
private void scrobble(MediaFile file, boolean submission) { |
||||
if (player.getClientId() == null) { // Don't scrobble REST players.
|
||||
audioScrobblerService.register(file, player.getUsername(), submission, null); |
||||
} |
||||
} |
||||
|
||||
public synchronized void setGain(float gain) { |
||||
this.gain = gain; |
||||
if (audioPlayer != null) { |
||||
audioPlayer.setGain(gain); |
||||
} |
||||
} |
||||
|
||||
public void setTranscodingService(TranscodingService transcodingService) { |
||||
this.transcodingService = transcodingService; |
||||
} |
||||
|
||||
public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) { |
||||
this.audioScrobblerService = audioScrobblerService; |
||||
} |
||||
|
||||
public void setStatusService(StatusService statusService) { |
||||
this.statusService = statusService; |
||||
} |
||||
|
||||
public void setSettingsService(SettingsService settingsService) { |
||||
this.settingsService = settingsService; |
||||
} |
||||
|
||||
public void setSecurityService(SecurityService securityService) { |
||||
this.securityService = securityService; |
||||
} |
||||
|
||||
public void setMediaFileService(MediaFileService mediaFileService) { |
||||
this.mediaFileService = mediaFileService; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
<databaseChangeLog |
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> |
||||
<changeSet id="add-player-mixer_001" author="biconou"> |
||||
<preConditions onFail="MARK_RAN"> |
||||
<not> |
||||
<columnExists tableName="player" columnName="mixer" /> |
||||
</not> |
||||
</preConditions> |
||||
<addColumn tableName="player"> |
||||
<column name="mixer" type="${varchar_type}"> |
||||
<constraints nullable="true" /> |
||||
</column> |
||||
</addColumn> |
||||
</changeSet> |
||||
</databaseChangeLog> |
@ -0,0 +1,35 @@ |
||||
<div id="javaJukeboxPlayerControlBar" class="bgcolor2" style="position:fixed; bottom:0; width:100%;padding-top:10px;padding-bottom: 5px"> |
||||
<table style="white-space:nowrap;"> |
||||
<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> |
||||
<td> |
||||
<img id="startIcon" src="<spring:theme code="castPlayImage"/>" onclick="onJavaJukeboxStart()" style="cursor:pointer"> |
||||
<img id="pauseIcon" src="<spring:theme code="castPauseImage"/>" onclick="onJavaJukeboxStop()" style="cursor:pointer; display:none"> |
||||
</td> |
||||
<td><span id="playingPositionDisplay" class="javaJukeBoxPlayerControlBarSongTime"/></td> |
||||
<td style="white-space:nowrap;"> |
||||
<div id="javaJukeboxSongPositionSlider"></div> |
||||
</td> |
||||
<td><span id="playingDurationDisplay" class="javaJukeBoxPlayerControlBarSongTime"/></td> |
||||
<td style="white-space:nowrap;"> |
||||
<img src="<spring:theme code="volumeImage"/>" alt=""> |
||||
</td> |
||||
<td style="white-space:nowrap;"> |
||||
<div id="javaJukeboxVolumeSlider" style="width:80px;height:4px"></div> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
|
||||
<script type="text/javascript" src="<c:url value='/script/playQueue/javaJukeboxPlayerControlBar.js'/>"></script> |
||||
<script type="text/javascript"> |
||||
initJavaJukeboxPlayerControlBar(); |
||||
</script> |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,102 @@ |
||||
|
||||
var songPlayingTimerId = null; |
||||
|
||||
var javaJukeboxPlayerModel = { |
||||
currentStreamUrl : null, |
||||
playing : false, |
||||
songDuration : null, |
||||
songPosition : 0 |
||||
} |
||||
|
||||
function refreshView() { |
||||
if (javaJukeboxPlayerModel.playing == true) { |
||||
if (songPlayingTimerId == null) { |
||||
songPlayingTimerId = setInterval(songPlayingTimer, 1000); |
||||
} |
||||
document.getElementById('startIcon').style.display = 'none'; |
||||
document.getElementById('pauseIcon').style.display = 'block'; |
||||
} else { |
||||
if (songPlayingTimerId != null) { |
||||
clearInterval(songPlayingTimerId); |
||||
songPlayingTimerId = null; |
||||
} |
||||
document.getElementById('pauseIcon').style.display = 'none'; |
||||
document.getElementById('startIcon').style.display = 'block'; |
||||
} |
||||
if (javaJukeboxPlayerModel.songDuration == null) { |
||||
$("#playingDurationDisplay").html("-:--"); |
||||
} else { |
||||
$("#playingDurationDisplay").html(songTimeAsString(javaJukeboxPlayerModel.songDuration)); |
||||
} |
||||
$("#playingPositionDisplay").html(songTimeAsString(javaJukeboxPlayerModel.songPosition)); |
||||
$("#javaJukeboxSongPositionSlider").slider("value",javaJukeboxPlayerModel.songPosition); |
||||
} |
||||
|
||||
function onJavaJukeboxStart() { |
||||
playQueueService.start(); |
||||
javaJukeboxPlayerModel.playing = true; |
||||
refreshView(); |
||||
} |
||||
|
||||
function onJavaJukeboxStop() { |
||||
playQueueService.stop(); |
||||
javaJukeboxPlayerModel.playing = false; |
||||
refreshView(); |
||||
} |
||||
|
||||
function onJavaJukeboxVolumeChanged() { |
||||
var value = $("#javaJukeboxVolumeSlider").slider("value"); |
||||
var gain = value / 100; |
||||
playQueueService.setGain(gain); |
||||
} |
||||
|
||||
function onJavaJukeboxPositionChanged() { |
||||
var pos = $("#javaJukeboxSongPositionSlider").slider("value"); |
||||
playQueueService.setJukeboxPosition(pos); |
||||
javaJukeboxPlayerModel.songPosition = pos; |
||||
refreshView(); |
||||
} |
||||
|
||||
function updateJavaJukeboxPlayerControlBar(song){ |
||||
if (song != null) { |
||||
var playingStream = song.streamUrl; |
||||
if (playingStream != javaJukeboxPlayerModel.currentStreamUrl) { |
||||
javaJukeboxPlayerModel.currentStreamUrl = playingStream; |
||||
newSongPlaying(song); |
||||
} |
||||
} |
||||
} |
||||
|
||||
function songTimeAsString(timeInSeconds) { |
||||
var m = moment.duration(timeInSeconds, 'seconds'); |
||||
var seconds = m.seconds(); |
||||
var secondsAsString = seconds; |
||||
if (seconds < 10) { |
||||
secondsAsString = "0" + seconds; |
||||
} |
||||
return m.minutes() + ":" + secondsAsString; |
||||
} |
||||
|
||||
function newSongPlaying(song) { |
||||
javaJukeboxPlayerModel.songDuration = song.duration; |
||||
$("#javaJukeboxSongPositionSlider").slider({max: javaJukeboxPlayerModel.songDuration, value: 0, animate: "fast", range: "min"}); |
||||
javaJukeboxPlayerModel.playing = true; |
||||
javaJukeboxPlayerModel.songPosition = 0; |
||||
refreshView(); |
||||
} |
||||
|
||||
function songPlayingTimer() { |
||||
javaJukeboxPlayerModel.songPosition += 1; |
||||
refreshView(); |
||||
} |
||||
|
||||
function initJavaJukeboxPlayerControlBar() { |
||||
$("#javaJukeboxSongPositionSlider").slider({max: 100, value: 0, animate: "fast", range: "min"}); |
||||
$("#javaJukeboxSongPositionSlider").slider("value",0); |
||||
$("#javaJukeboxSongPositionSlider").on("slidestop", onJavaJukeboxPositionChanged); |
||||
|
||||
$("#javaJukeboxVolumeSlider").slider({max: 100, value: 50, animate: "fast", range: "min"}); |
||||
$("#javaJukeboxVolumeSlider").on("slidestop", onJavaJukeboxVolumeChanged); |
||||
|
||||
refreshView(); |
||||
} |
Loading…
Reference in new issue