Merge pull request #563 from biconou/squash
Introduction of a new kind of jukebox player based on the javasound api.
This commit is contained in:
+20
-1
@@ -31,6 +31,25 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Java Audio Player and needed dependencies
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.github.biconou</groupId>
|
||||
<artifactId>AudioPlayer</artifactId>
|
||||
<version>0.2.3</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- -->
|
||||
|
||||
<!-- Metrics
|
||||
Metrics is a cool framework used here
|
||||
to compute musures and statistics during automated testing
|
||||
@@ -317,7 +336,7 @@
|
||||
<version>${cxf.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId> org.springframework</groupId>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
|
||||
@@ -89,27 +89,22 @@ public class PlayQueueService {
|
||||
}
|
||||
|
||||
public PlayQueueInfo start() throws Exception {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
return doStart(request, response);
|
||||
Player player = resolvePlayer();
|
||||
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
|
||||
if (player.isJukebox()) {
|
||||
jukeboxService.start(player);
|
||||
}
|
||||
return convert(resolveHttpServletRequest(), player, true);
|
||||
}
|
||||
|
||||
public PlayQueueInfo doStart(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
|
||||
return convert(request, player, true);
|
||||
}
|
||||
|
||||
public PlayQueueInfo stop() throws Exception {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
return doStop(request, response);
|
||||
}
|
||||
|
||||
public PlayQueueInfo doStop(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
Player player = resolvePlayer();
|
||||
player.getPlayQueue().setStatus(PlayQueue.Status.STOPPED);
|
||||
return convert(request, player, true);
|
||||
if (player.isJukebox()) {
|
||||
jukeboxService.stop(player);
|
||||
}
|
||||
return convert(resolveHttpServletRequest(), player, true);
|
||||
}
|
||||
|
||||
public PlayQueueInfo toggleStartStop() throws Exception {
|
||||
@@ -129,16 +124,17 @@ public class PlayQueueService {
|
||||
}
|
||||
|
||||
public PlayQueueInfo skip(int index) throws Exception {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
return doSkip(request, response, index, 0);
|
||||
return doSkip(index, 0);
|
||||
}
|
||||
|
||||
public PlayQueueInfo doSkip(HttpServletRequest request, HttpServletResponse response, int index, int offset) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
public PlayQueueInfo doSkip(int index, int offset) throws Exception {
|
||||
Player player = resolvePlayer();
|
||||
player.getPlayQueue().setIndex(index);
|
||||
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
|
||||
return convert(request, player, serverSidePlaylist, offset);
|
||||
if (serverSidePlaylist && player.isJukebox()) {
|
||||
jukeboxService.skip(player,index,offset);
|
||||
}
|
||||
return convert(resolveHttpServletRequest(), player, serverSidePlaylist, offset);
|
||||
}
|
||||
|
||||
public PlayQueueInfo reloadSearchCriteria() throws Exception {
|
||||
@@ -202,23 +198,24 @@ public class PlayQueueService {
|
||||
|
||||
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
|
||||
if (serverSidePlaylist && currentIndex != -1) {
|
||||
doSkip(request, response, currentIndex, (int) (positionMillis / 1000L));
|
||||
doSkip(currentIndex, (int) (positionMillis / 1000L));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public PlayQueueInfo play(int id) throws Exception {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
HttpServletRequest request = resolveHttpServletRequest();
|
||||
HttpServletResponse response = resolveHttpServletResponse();
|
||||
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
MediaFile file = mediaFileService.getMediaFile(id);
|
||||
|
||||
List<MediaFile> songs;
|
||||
|
||||
if (file.isFile()) {
|
||||
String username = securityService.getCurrentUsername(request);
|
||||
boolean queueFollowingSongs = settingsService.getUserSettings(username).isQueueFollowingSongs();
|
||||
List<MediaFile> songs;
|
||||
if (queueFollowingSongs) {
|
||||
MediaFile dir = mediaFileService.getParentOf(file);
|
||||
songs = mediaFileService.getChildrenOf(dir, true, false, true);
|
||||
@@ -229,11 +226,10 @@ public class PlayQueueService {
|
||||
} else {
|
||||
songs = Arrays.asList(file);
|
||||
}
|
||||
return doPlay(request, player, songs).setStartPlayerAt(0);
|
||||
} else {
|
||||
List<MediaFile> songs = mediaFileService.getDescendantsOf(file, true);
|
||||
return doPlay(request, player, songs).setStartPlayerAt(0);
|
||||
songs = mediaFileService.getDescendantsOf(file, true);
|
||||
}
|
||||
return doPlay(request, player, songs).setStartPlayerAt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -422,6 +418,9 @@ public class PlayQueueService {
|
||||
}
|
||||
player.getPlayQueue().addFiles(false, files);
|
||||
player.getPlayQueue().setRandomSearchCriteria(null);
|
||||
if (player.isJukebox()) {
|
||||
jukeboxService.play(player);
|
||||
}
|
||||
return convert(request, player, true);
|
||||
}
|
||||
|
||||
@@ -461,69 +460,81 @@ public class PlayQueueService {
|
||||
return doAdd(request, response, new int[]{id}, index);
|
||||
}
|
||||
|
||||
public PlayQueueInfo doAdd(HttpServletRequest request, HttpServletResponse response, int[] ids, Integer index) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
/**
|
||||
* TODO This method should be moved to a real PlayQueueService not dedicated to Ajax DWR.
|
||||
* @param playQueue
|
||||
* @param ids
|
||||
* @param index
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public PlayQueue addMediaFilesToPlayQueue(PlayQueue playQueue,int[] ids, Integer index, boolean removeVideoFiles) throws Exception {
|
||||
List<MediaFile> files = new ArrayList<MediaFile>(ids.length);
|
||||
for (int id : ids) {
|
||||
MediaFile ancestor = mediaFileService.getMediaFile(id);
|
||||
files.addAll(mediaFileService.getDescendantsOf(ancestor, true));
|
||||
}
|
||||
if (player.isWeb()) {
|
||||
if (removeVideoFiles) {
|
||||
mediaFileService.removeVideoFiles(files);
|
||||
}
|
||||
if (index != null) {
|
||||
player.getPlayQueue().addFilesAt(files, index);
|
||||
playQueue.addFilesAt(files, index);
|
||||
} else {
|
||||
player.getPlayQueue().addFiles(true, files);
|
||||
playQueue.addFiles(true, files);
|
||||
}
|
||||
player.getPlayQueue().setRandomSearchCriteria(null);
|
||||
playQueue.setRandomSearchCriteria(null);
|
||||
return playQueue;
|
||||
}
|
||||
|
||||
public PlayQueueInfo doAdd(HttpServletRequest request, HttpServletResponse response, int[] ids, Integer index) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
boolean removeVideoFiles = false;
|
||||
if (player.isWeb()) {
|
||||
removeVideoFiles = true;
|
||||
}
|
||||
addMediaFilesToPlayQueue(player.getPlayQueue(), ids, index, removeVideoFiles);
|
||||
return convert(request, player, false);
|
||||
}
|
||||
|
||||
public PlayQueueInfo doSet(HttpServletRequest request, HttpServletResponse response, int[] ids) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
PlayQueue playQueue = player.getPlayQueue();
|
||||
/**
|
||||
* TODO This method should be moved to a real PlayQueueService not dedicated to Ajax DWR.
|
||||
* @param playQueue
|
||||
* @param ids
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public PlayQueue resetPlayQueue(PlayQueue playQueue,int[] ids, boolean removeVideoFiles) throws Exception {
|
||||
MediaFile currentFile = playQueue.getCurrentFile();
|
||||
PlayQueue.Status status = playQueue.getStatus();
|
||||
|
||||
playQueue.clear();
|
||||
PlayQueueInfo result = doAdd(request, response, ids, null);
|
||||
addMediaFilesToPlayQueue(playQueue, ids, null,removeVideoFiles);
|
||||
|
||||
int index = currentFile == null ? -1 : playQueue.getFiles().indexOf(currentFile);
|
||||
playQueue.setIndex(index);
|
||||
playQueue.setStatus(status);
|
||||
return result;
|
||||
return playQueue;
|
||||
}
|
||||
|
||||
public PlayQueueInfo clear() throws Exception {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
return doClear(request, response);
|
||||
}
|
||||
|
||||
public PlayQueueInfo doClear(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
Player player = resolvePlayer();
|
||||
player.getPlayQueue().clear();
|
||||
boolean serverSidePlaylist = !player.isExternalWithPlaylist();
|
||||
return convert(request, player, serverSidePlaylist);
|
||||
return convert(resolveHttpServletRequest(), player, serverSidePlaylist);
|
||||
}
|
||||
|
||||
|
||||
public PlayQueueInfo shuffle() throws Exception {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
return doShuffle(request, response);
|
||||
Player player = resolvePlayer();
|
||||
player.getPlayQueue().shuffle();
|
||||
return convert(resolveHttpServletRequest(), player, false);
|
||||
}
|
||||
|
||||
public PlayQueueInfo doShuffle(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
player.getPlayQueue().shuffle();
|
||||
return convert(request, player, false);
|
||||
}
|
||||
|
||||
public PlayQueueInfo remove(int index) throws Exception {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
return doRemove(request, response, index);
|
||||
Player player = resolvePlayer();
|
||||
player.getPlayQueue().removeFileAt(index);
|
||||
return convert(resolveHttpServletRequest(), player, false);
|
||||
}
|
||||
|
||||
public PlayQueueInfo toggleStar(int index) throws Exception {
|
||||
@@ -629,10 +640,6 @@ public class PlayQueueService {
|
||||
return convert(request, player, false);
|
||||
}
|
||||
|
||||
public void setGain(float gain) {
|
||||
jukeboxService.setGain(gain);
|
||||
}
|
||||
|
||||
private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean serverSidePlaylist) throws Exception {
|
||||
return convert(request, player, serverSidePlaylist, 0);
|
||||
}
|
||||
@@ -640,9 +647,9 @@ public class PlayQueueService {
|
||||
private PlayQueueInfo convert(HttpServletRequest request, Player player, boolean serverSidePlaylist, int offset) throws Exception {
|
||||
String url = NetworkService.getBaseUrl(request);
|
||||
|
||||
if (serverSidePlaylist && player.isJukebox()) {
|
||||
jukeboxService.updateJukebox(player, offset);
|
||||
}
|
||||
/* if (serverSidePlaylist && player.isJukebox()) {
|
||||
updateJukebox(player, offset);
|
||||
} */
|
||||
boolean isCurrentPlayer = player.getIpAddress() != null && player.getIpAddress().equals(request.getRemoteAddr());
|
||||
|
||||
boolean m3uSupported = player.isExternal() || player.isExternalWithPlaylist();
|
||||
@@ -671,7 +678,10 @@ public class PlayQueueService {
|
||||
coverArtUrl, remoteCoverArtUrl));
|
||||
}
|
||||
boolean isStopEnabled = playQueue.getStatus() == PlayQueue.Status.PLAYING && !player.isExternalWithPlaylist();
|
||||
float gain = jukeboxService.getGain();
|
||||
|
||||
float gain = 0.0f;
|
||||
gain = jukeboxService.getGain(player);
|
||||
|
||||
return new PlayQueueInfo(entries, isStopEnabled, playQueue.isRepeatEnabled(), playQueue.isRadioEnabled(), serverSidePlaylist, gain);
|
||||
}
|
||||
|
||||
@@ -704,6 +714,43 @@ public class PlayQueueService {
|
||||
return playerService.getPlayer(request, response);
|
||||
}
|
||||
|
||||
private Player resolvePlayer() {
|
||||
return getCurrentPlayer(resolveHttpServletRequest(), resolveHttpServletResponse());
|
||||
}
|
||||
|
||||
private HttpServletRequest resolveHttpServletRequest() {
|
||||
return WebContextFactory.get().getHttpServletRequest();
|
||||
}
|
||||
|
||||
private HttpServletResponse resolveHttpServletResponse() {
|
||||
return WebContextFactory.get().getHttpServletResponse();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Methods dedicated to jukebox
|
||||
//
|
||||
|
||||
public void setGain(float gain) {
|
||||
HttpServletRequest request = WebContextFactory.get().getHttpServletRequest();
|
||||
HttpServletResponse response = WebContextFactory.get().getHttpServletResponse();
|
||||
Player player = getCurrentPlayer(request, response);
|
||||
if (player != null) {
|
||||
jukeboxService.setGain(player,gain);
|
||||
}
|
||||
}
|
||||
|
||||
public void setJukeboxPosition(int positionInSeconds) {
|
||||
Player player = resolvePlayer();
|
||||
jukeboxService.setPosition(player,positionInSeconds);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// End : Methods dedicated to jukebox
|
||||
//
|
||||
|
||||
public void setPlayerService(PlayerService playerService) {
|
||||
this.playerService = playerService;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class PlayerSettingsCommand {
|
||||
private EnumHolder[] transcodeSchemeHolders;
|
||||
private Player[] players;
|
||||
private boolean isAdmin;
|
||||
private String javaJukeboxMixer;
|
||||
private String[] javaJukeboxMixers;
|
||||
|
||||
public String getPlayerId() {
|
||||
return playerId;
|
||||
@@ -209,6 +211,22 @@ public class PlayerSettingsCommand {
|
||||
public void setReloadNeeded(boolean reloadNeeded) {
|
||||
}
|
||||
|
||||
public String getJavaJukeboxMixer() {
|
||||
return javaJukeboxMixer;
|
||||
}
|
||||
|
||||
public void setJavaJukeboxMixer(String javaJukeboxMixer) {
|
||||
this.javaJukeboxMixer = javaJukeboxMixer;
|
||||
}
|
||||
|
||||
public String[] getJavaJukeboxMixers() {
|
||||
return javaJukeboxMixers;
|
||||
}
|
||||
|
||||
public void setJavaJukeboxMixers(String[] javaJukeboxMixers) {
|
||||
this.javaJukeboxMixers = javaJukeboxMixers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the transcoding and whether it is active for the given player.
|
||||
*/
|
||||
|
||||
@@ -70,16 +70,4 @@ public class PlayQueueController {
|
||||
map.put("autoHide", userSettings.isAutoHidePlayQueue());
|
||||
return new ModelAndView("playQueue","model",map);
|
||||
}
|
||||
|
||||
public void setPlayerService(PlayerService playerService) {
|
||||
this.playerService = playerService;
|
||||
}
|
||||
|
||||
public void setSecurityService(SecurityService securityService) {
|
||||
this.securityService = securityService;
|
||||
}
|
||||
|
||||
public void setSettingsService(SettingsService settingsService) {
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package org.airsonic.player.controller;
|
||||
|
||||
import com.github.biconou.AudioPlayer.AudioSystemUtils;
|
||||
import org.airsonic.player.command.PlayerSettingsCommand;
|
||||
import org.airsonic.player.domain.*;
|
||||
import org.airsonic.player.service.PlayerService;
|
||||
@@ -36,6 +37,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -102,6 +104,9 @@ public class PlayerSettingsController {
|
||||
command.setPlayers(players.toArray(new Player[players.size()]));
|
||||
command.setAdmin(user.isAdminRole());
|
||||
|
||||
command.setJavaJukeboxMixers(Arrays.stream(AudioSystemUtils.listAllMixers()).map(info -> info.getName()).toArray(String[]::new));
|
||||
command.setJavaJukeboxMixer(player.getJavaJukeboxMixer());
|
||||
|
||||
model.addAttribute("command",command);
|
||||
}
|
||||
|
||||
@@ -115,6 +120,7 @@ public class PlayerSettingsController {
|
||||
player.setName(StringUtils.trimToNull(command.getName()));
|
||||
player.setTranscodeScheme(TranscodeScheme.valueOf(command.getTranscodeSchemeName()));
|
||||
player.setTechnology(PlayerTechnology.valueOf(command.getTechnologyName()));
|
||||
player.setJavaJukeboxMixer(command.getJavaJukeboxMixer());
|
||||
|
||||
playerService.updatePlayer(player);
|
||||
transcodingService.setTranscodingsForPlayer(player, command.getActiveTranscodingIds());
|
||||
|
||||
+50
-30
@@ -29,6 +29,7 @@ import org.airsonic.player.dao.MediaFileDao;
|
||||
import org.airsonic.player.dao.PlayQueueDao;
|
||||
import org.airsonic.player.domain.*;
|
||||
import org.airsonic.player.domain.Bookmark;
|
||||
import org.airsonic.player.domain.PlayQueue;
|
||||
import org.airsonic.player.service.*;
|
||||
import org.airsonic.player.util.Pair;
|
||||
import org.airsonic.player.util.StringUtil;
|
||||
@@ -833,51 +834,70 @@ public class SubsonicRESTController {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = playerService.getPlayer(request, response);
|
||||
|
||||
boolean returnPlaylist = false;
|
||||
String action = getRequiredStringParameter(request, "action");
|
||||
if ("start".equals(action)) {
|
||||
playQueueService.doStart(request, response);
|
||||
} else if ("stop".equals(action)) {
|
||||
playQueueService.doStop(request, response);
|
||||
} else if ("skip".equals(action)) {
|
||||
|
||||
switch (action) {
|
||||
case "start":
|
||||
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
|
||||
jukeboxService.start(player);
|
||||
break;
|
||||
case "stop":
|
||||
player.getPlayQueue().setStatus(PlayQueue.Status.STOPPED);
|
||||
jukeboxService.stop(player);
|
||||
break;
|
||||
case "skip":
|
||||
int index = getRequiredIntParameter(request, "index");
|
||||
int offset = getIntParameter(request, "offset", 0);
|
||||
playQueueService.doSkip(request, response, index, offset);
|
||||
} else if ("add".equals(action)) {
|
||||
player.getPlayQueue().setIndex(index);
|
||||
jukeboxService.skip(player,index,offset);
|
||||
break;
|
||||
case "add":
|
||||
int[] ids = getIntParameters(request, "id");
|
||||
playQueueService.doAdd(request, response, ids, null);
|
||||
} else if ("set".equals(action)) {
|
||||
int[] ids = getIntParameters(request, "id");
|
||||
playQueueService.doSet(request, response, ids);
|
||||
} else if ("clear".equals(action)) {
|
||||
playQueueService.doClear(request, response);
|
||||
} else if ("remove".equals(action)) {
|
||||
int index = getRequiredIntParameter(request, "index");
|
||||
playQueueService.doRemove(request, response, index);
|
||||
} else if ("shuffle".equals(action)) {
|
||||
playQueueService.doShuffle(request, response);
|
||||
} else if ("setGain".equals(action)) {
|
||||
playQueueService.addMediaFilesToPlayQueue(player.getPlayQueue(),ids,null,true);
|
||||
break;
|
||||
case "set":
|
||||
ids = getIntParameters(request, "id");
|
||||
playQueueService.resetPlayQueue(player.getPlayQueue(),ids,true);
|
||||
break;
|
||||
case "clear":
|
||||
player.getPlayQueue().clear();
|
||||
break;
|
||||
case "remove":
|
||||
index = getRequiredIntParameter(request, "index");
|
||||
player.getPlayQueue().removeFileAt(index);
|
||||
break;
|
||||
case "shuffle":
|
||||
player.getPlayQueue().shuffle();
|
||||
break;
|
||||
case "setGain":
|
||||
float gain = getRequiredFloatParameter(request, "gain");
|
||||
jukeboxService.setGain(gain);
|
||||
} else if ("get".equals(action)) {
|
||||
jukeboxService.setGain(player,gain);
|
||||
break;
|
||||
case "get":
|
||||
returnPlaylist = true;
|
||||
} else if ("status".equals(action)) {
|
||||
break;
|
||||
case "status":
|
||||
// No action necessary.
|
||||
} else {
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown jukebox action: '" + action + "'.");
|
||||
}
|
||||
|
||||
Player player = playerService.getPlayer(request, response);
|
||||
String username = securityService.getCurrentUsername(request);
|
||||
Player jukeboxPlayer = jukeboxService.getPlayer();
|
||||
boolean controlsJukebox = jukeboxPlayer != null && jukeboxPlayer.getId().equals(player.getId());
|
||||
org.airsonic.player.domain.PlayQueue playQueue = player.getPlayQueue();
|
||||
PlayQueue playQueue = player.getPlayQueue();
|
||||
|
||||
// this variable is only needed for the JukeboxLegacySubsonicService. To be removed.
|
||||
boolean controlsJukebox = jukeboxService.canControl(player);
|
||||
|
||||
int currentIndex = controlsJukebox && !playQueue.isEmpty() ? playQueue.getIndex() : -1;
|
||||
boolean playing = controlsJukebox && !playQueue.isEmpty() && playQueue.getStatus() == org.airsonic.player.domain.PlayQueue.Status.PLAYING;
|
||||
float gain = jukeboxService.getGain();
|
||||
int position = controlsJukebox && !playQueue.isEmpty() ? jukeboxService.getPosition() : 0;
|
||||
boolean playing = controlsJukebox && !playQueue.isEmpty() && playQueue.getStatus() == PlayQueue.Status.PLAYING;
|
||||
float gain;
|
||||
int position;
|
||||
gain = jukeboxService.getGain(player);
|
||||
position = controlsJukebox && !playQueue.isEmpty() ? jukeboxService.getPosition(player) : 0;
|
||||
|
||||
Response res = createResponse();
|
||||
if (returnPlaylist) {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package org.airsonic.player.dao;
|
||||
|
||||
import org.airsonic.player.domain.*;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
@@ -40,7 +41,7 @@ public class PlayerDao extends AbstractDao {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PlayerDao.class);
|
||||
private static final String INSERT_COLUMNS = "name, type, username, ip_address, auto_control_enabled, m3u_bom_enabled, " +
|
||||
"last_seen, cover_art_scheme, transcode_scheme, dynamic_ip, technology, client_id";
|
||||
"last_seen, cover_art_scheme, transcode_scheme, dynamic_ip, technology, client_id, mixer";
|
||||
private static final String QUERY_COLUMNS = "id, " + INSERT_COLUMNS;
|
||||
|
||||
private PlayerRowMapper rowMapper = new PlayerRowMapper();
|
||||
@@ -81,9 +82,13 @@ public class PlayerDao extends AbstractDao {
|
||||
* @return The player with the given ID, or <code>null</code> if no such player exists.
|
||||
*/
|
||||
public Player getPlayerById(String id) {
|
||||
if (StringUtils.isBlank(id)) {
|
||||
return null;
|
||||
} else {
|
||||
String sql = "select " + QUERY_COLUMNS + " from player where id=?";
|
||||
return queryOne(sql, rowMapper, id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new player.
|
||||
@@ -103,7 +108,7 @@ public class PlayerDao extends AbstractDao {
|
||||
player.getIpAddress(), player.isAutoControlEnabled(), player.isM3uBomEnabled(),
|
||||
player.getLastSeen(), CoverArtScheme.MEDIUM.name(),
|
||||
player.getTranscodeScheme().name(), player.isDynamicIp(),
|
||||
player.getTechnology().name(), player.getClientId());
|
||||
player.getTechnology().name(), player.getClientId(), player.getJavaJukeboxMixer());
|
||||
addPlaylist(player);
|
||||
|
||||
LOG.info("Created player " + id + '.');
|
||||
@@ -154,12 +159,13 @@ public class PlayerDao extends AbstractDao {
|
||||
"transcode_scheme = ?, " +
|
||||
"dynamic_ip = ?, " +
|
||||
"technology = ?, " +
|
||||
"client_id = ? " +
|
||||
"client_id = ?, " +
|
||||
"mixer = ? " +
|
||||
"where id = ?";
|
||||
update(sql, player.getName(), player.getType(), player.getUsername(),
|
||||
player.getIpAddress(), player.isAutoControlEnabled(), player.isM3uBomEnabled(),
|
||||
player.getLastSeen(), player.getTranscodeScheme().name(), player.isDynamicIp(),
|
||||
player.getTechnology().name(), player.getClientId(), player.getId());
|
||||
player.getTechnology().name(), player.getClientId(), player.getJavaJukeboxMixer(), player.getId());
|
||||
}
|
||||
|
||||
private void addPlaylist(Player player) {
|
||||
@@ -188,6 +194,7 @@ public class PlayerDao extends AbstractDao {
|
||||
player.setDynamicIp(rs.getBoolean(col++));
|
||||
player.setTechnology(PlayerTechnology.valueOf(rs.getString(col++)));
|
||||
player.setClientId(rs.getString(col++));
|
||||
player.setJavaJukeboxMixer(rs.getString(col++));
|
||||
|
||||
addPlaylist(player);
|
||||
return player;
|
||||
|
||||
@@ -44,6 +44,7 @@ public class Player {
|
||||
private Date lastSeen;
|
||||
private TranscodeScheme transcodeScheme = TranscodeScheme.OFF;
|
||||
private PlayQueue playQueue;
|
||||
private String javaJukeboxMixer;
|
||||
|
||||
/**
|
||||
* Returns the player ID.
|
||||
@@ -120,7 +121,11 @@ public class Player {
|
||||
}
|
||||
|
||||
public boolean isJukebox() {
|
||||
return technology == PlayerTechnology.JUKEBOX;
|
||||
return (technology == PlayerTechnology.JUKEBOX || technology == PlayerTechnology.JAVA_JUKEBOX);
|
||||
}
|
||||
|
||||
public boolean isJavaJukebox() {
|
||||
return (technology == PlayerTechnology.JAVA_JUKEBOX);
|
||||
}
|
||||
|
||||
public boolean isExternal() {
|
||||
@@ -326,6 +331,16 @@ public class Player {
|
||||
return "Player " + id;
|
||||
}
|
||||
|
||||
|
||||
public void setJavaJukeboxMixer(String javaJukeboxMixer) {
|
||||
this.javaJukeboxMixer = javaJukeboxMixer;
|
||||
}
|
||||
|
||||
public String getJavaJukeboxMixer() {
|
||||
return javaJukeboxMixer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string representation of the player.
|
||||
*
|
||||
@@ -336,4 +351,5 @@ public class Player {
|
||||
public String toString() {
|
||||
return getDescription();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ public enum PlayerTechnology {
|
||||
/**
|
||||
* Plays music directly on the audio device of the Airsonic server.
|
||||
*/
|
||||
JUKEBOX
|
||||
JUKEBOX,
|
||||
|
||||
/**
|
||||
* Jukebox player that uses the Java Sound API.
|
||||
*/
|
||||
JAVA_JUKEBOX
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+210
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -20,191 +20,163 @@
|
||||
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
|
||||
*
|
||||
* @author R?mi Cocula
|
||||
*/
|
||||
@Service
|
||||
public class JukeboxService implements AudioPlayer.Listener {
|
||||
public class JukeboxService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JukeboxService.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(JukeboxService.class);
|
||||
|
||||
private AudioPlayer audioPlayer;
|
||||
@Autowired
|
||||
private TranscodingService transcodingService;
|
||||
private JukeboxLegacySubsonicService jukeboxLegacySubsonicService;
|
||||
@Autowired
|
||||
private AudioScrobblerService audioScrobblerService;
|
||||
@Autowired
|
||||
private StatusService statusService;
|
||||
@Autowired
|
||||
private SettingsService settingsService;
|
||||
@Autowired
|
||||
private SecurityService securityService;
|
||||
private JukeboxJavaService jukeboxJavaService;
|
||||
|
||||
private Player player;
|
||||
private TransferStatus status;
|
||||
private MediaFile currentPlayingFile;
|
||||
private float gain = AudioPlayer.DEFAULT_GAIN;
|
||||
private int offset;
|
||||
@Autowired
|
||||
private MediaFileService mediaFileService;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
public void setGain(Player airsonicPlayer,float gain) {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
jukeboxLegacySubsonicService.setGain(gain);
|
||||
break;
|
||||
case JAVA_JUKEBOX:
|
||||
jukeboxJavaService.setGain(airsonicPlayer,gain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
public void setPosition(Player airsonicPlayer, int positionInSeconds) {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
throw new UnsupportedOperationException();
|
||||
case JAVA_JUKEBOX:
|
||||
jukeboxJavaService.setPosition(airsonicPlayer,positionInSeconds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
public float getGain(Player airsonicPlayer) {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
return jukeboxLegacySubsonicService.getGain();
|
||||
case JAVA_JUKEBOX:
|
||||
return jukeboxJavaService.getGain(airsonicPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the player which currently uses the jukebox.
|
||||
* This method should be removed when the jukebox is controlled only through rest api.
|
||||
*
|
||||
* @return The player, may be {@code null}.
|
||||
* @param airsonicPlayer
|
||||
* @param offset
|
||||
* @throws Exception
|
||||
*/
|
||||
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);
|
||||
@Deprecated
|
||||
public void updateJukebox(Player airsonicPlayer, int offset) throws Exception {
|
||||
if (airsonicPlayer.getTechnology().equals(PlayerTechnology.JUKEBOX)) {
|
||||
jukeboxLegacySubsonicService.updateJukebox(airsonicPlayer,offset);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setGain(float gain) {
|
||||
this.gain = gain;
|
||||
if (audioPlayer != null) {
|
||||
audioPlayer.setGain(gain);
|
||||
public int getPosition(Player airsonicPlayer) {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
return jukeboxLegacySubsonicService.getPosition();
|
||||
case JAVA_JUKEBOX:
|
||||
return jukeboxJavaService.getPosition(airsonicPlayer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here due to legacy considerations and should be removed
|
||||
* if the jukeboxLegacySubsonicService is removed.
|
||||
* @param airsonicPlayer
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean canControl(Player airsonicPlayer) {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
if (jukeboxLegacySubsonicService.getPlayer() == null) {
|
||||
return false;
|
||||
} else {
|
||||
return jukeboxLegacySubsonicService.getPlayer().getId().equals(airsonicPlayer.getId());
|
||||
}
|
||||
case JAVA_JUKEBOX:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the playQueue of a jukebox player starting at the first item of the queue.
|
||||
*
|
||||
* @param airsonicPlayer
|
||||
* @throws Exception
|
||||
*/
|
||||
public void play(Player airsonicPlayer) throws Exception {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
jukeboxLegacySubsonicService.updateJukebox(airsonicPlayer,0);
|
||||
break;
|
||||
case JAVA_JUKEBOX:
|
||||
jukeboxJavaService.play(airsonicPlayer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTranscodingService(TranscodingService transcodingService) {
|
||||
this.transcodingService = transcodingService;
|
||||
|
||||
public void start(Player airsonicPlayer) throws Exception {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
jukeboxLegacySubsonicService.updateJukebox(airsonicPlayer,0);
|
||||
break;
|
||||
case JAVA_JUKEBOX:
|
||||
jukeboxJavaService.start(airsonicPlayer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) {
|
||||
this.audioScrobblerService = audioScrobblerService;
|
||||
public void stop(Player airsonicPlayer) throws Exception {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
jukeboxLegacySubsonicService.updateJukebox(airsonicPlayer,0);
|
||||
break;
|
||||
case JAVA_JUKEBOX:
|
||||
jukeboxJavaService.stop(airsonicPlayer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatusService(StatusService statusService) {
|
||||
this.statusService = statusService;
|
||||
public void skip(Player airsonicPlayer,int index,int offset) throws Exception {
|
||||
switch (airsonicPlayer.getTechnology()) {
|
||||
case JUKEBOX:
|
||||
jukeboxLegacySubsonicService.updateJukebox(airsonicPlayer,offset);
|
||||
break;
|
||||
case JAVA_JUKEBOX:
|
||||
jukeboxJavaService.skip(airsonicPlayer,index,offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSettingsService(SettingsService settingsService) {
|
||||
this.settingsService = settingsService;
|
||||
|
||||
/* properties setters */
|
||||
|
||||
public void setJukeboxLegacySubsonicService(JukeboxLegacySubsonicService jukeboxLegacySubsonicService) {
|
||||
this.jukeboxLegacySubsonicService = jukeboxLegacySubsonicService;
|
||||
}
|
||||
|
||||
public void setSecurityService(SecurityService securityService) {
|
||||
this.securityService = securityService;
|
||||
public void setJukeboxJavaService(JukeboxJavaService jukeboxJavaService) {
|
||||
this.jukeboxJavaService = jukeboxJavaService;
|
||||
}
|
||||
|
||||
public void setMediaFileService(MediaFileService mediaFileService) {
|
||||
this.mediaFileService = mediaFileService;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,8 +182,12 @@ public class PlayerService {
|
||||
* @return The player with the given ID, or <code>null</code> if no such player exists.
|
||||
*/
|
||||
public Player getPlayerById(String id) {
|
||||
if (StringUtils.isBlank(id)) {
|
||||
return null;
|
||||
} else {
|
||||
return playerDao.getPlayerById(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given player is connected.
|
||||
|
||||
@@ -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>
|
||||
@@ -2,5 +2,6 @@
|
||||
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">
|
||||
<include file="add-player-mixer.xml" relativeToChangelogFile="true"/>
|
||||
<include file="mediaelement.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
@@ -452,11 +452,14 @@ playersettings.technology.web.title=Web player
|
||||
playersettings.technology.external.title=External player
|
||||
playersettings.technology.external_with_playlist.title=External player with playlist
|
||||
playersettings.technology.jukebox.title=Jukebox
|
||||
playersettings.technology.java_jukebox.title=Java Jukebox
|
||||
playersettings.technology.web.text=Play music directly in the web browser using the integrated Flash player.
|
||||
playersettings.technology.external.text=Play music in your favorite player, such as WinAmp or Windows Media Player.
|
||||
playersettings.technology.external_with_playlist.text=Same as above, but the playlist is managed by the player, rather than the Airsonic server. In this mode, skipping within songs is possible.
|
||||
playersettings.technology.jukebox.text=Play music directly on the audio device of the Airsonic server. (Authorized users only).
|
||||
playersettings.technology.java_jukebox.text=Jukebox player that uses the Java Sound API. (Authorized users only).
|
||||
playersettings.name=Player name
|
||||
playersettings.javaJukeboxMixer=Audio device
|
||||
playersettings.maxbitrate=Max bitrate
|
||||
playersettings.notranscoder=<em>Notice:</em> Transcoders does not appear to be installed.<br>Click Help button for more information.
|
||||
playersettings.autocontrol=Control player automatically
|
||||
|
||||
@@ -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>
|
||||
@@ -3,6 +3,7 @@
|
||||
<html><head>
|
||||
<%@ include file="head.jsp" %>
|
||||
<%@ include file="jquery.jsp" %>
|
||||
<script type="text/javascript" src="<c:url value="/script/moment-2.18.1.min.js"/>"></script>
|
||||
<link type="text/css" rel="stylesheet" href="<c:url value="/script/webfx/luna.css"/>">
|
||||
<script type="text/javascript" src="<c:url value="/script/scripts-2.0.js"/>"></script>
|
||||
<script type="text/javascript" src="<c:url value="/dwr/interface/nowPlayingService.js"/>"></script>
|
||||
@@ -257,6 +258,9 @@
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
currentStreamUrl = songs[index].streamUrl;
|
||||
if (isJavaJukeboxPresent()) {
|
||||
updateJavaJukeboxPlayerControlBar(songs[index]);
|
||||
}
|
||||
playQueueService.skip(index, playQueueCallback);
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
@@ -399,6 +403,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
function isJavaJukeboxPresent() {
|
||||
return $("#javaJukeboxPlayerControlBarContainer").length==1;
|
||||
}
|
||||
|
||||
function playQueueCallback(playQueue) {
|
||||
songs = playQueue.entries;
|
||||
repeatEnabled = playQueue.repeatEnabled;
|
||||
@@ -446,6 +454,9 @@
|
||||
}
|
||||
if ($("#currentImage" + id) && song.streamUrl == currentStreamUrl) {
|
||||
$("#currentImage" + id).show();
|
||||
if (isJavaJukeboxPresent()) {
|
||||
updateJavaJukeboxPlayerControlBar(song);
|
||||
}
|
||||
}
|
||||
if ($("#title" + id)) {
|
||||
$("#title" + id).html(song.title);
|
||||
@@ -658,7 +669,14 @@
|
||||
|
||||
</script>
|
||||
|
||||
<div class="bgcolor2" style="position:fixed; bottom:0; width:100%;padding-top:10px;">
|
||||
<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;">
|
||||
<tr style="white-space:nowrap;">
|
||||
<c:if test="${model.user.settingsRole and fn:length(model.players) gt 1}">
|
||||
@@ -766,7 +784,10 @@
|
||||
</td>
|
||||
|
||||
</tr></table>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -10,6 +10,34 @@
|
||||
<script type="text/javascript" src="<c:url value="/script/wz_tooltip.js"/>"></script>
|
||||
<script type="text/javascript" src="<c:url value="/script/tip_balloon.js"/>"></script>
|
||||
|
||||
<script lang="javascript">
|
||||
|
||||
function hideAllTechnologyDepends() {
|
||||
$('.technologyDepends').hide();
|
||||
}
|
||||
|
||||
function showTechnologyDepends(technologyName) {
|
||||
var selector = '.technologyDepends.' + technologyName;
|
||||
$(selector).show();
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$('.technologyRadio').click(function() {
|
||||
hideAllTechnologyDepends();
|
||||
var technologyName = $(this).val();
|
||||
showTechnologyDepends(technologyName);
|
||||
});
|
||||
|
||||
hideAllTechnologyDepends();
|
||||
$('.technologyRadio:checked').each(function() {
|
||||
var technologyName = $(this).val();
|
||||
showTechnologyDepends(technologyName);
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<c:import url="settingsHeader.jsp">
|
||||
<c:param name="cat" value="player"/>
|
||||
<c:param name="toast" value="${settings_toast}"/>
|
||||
@@ -60,7 +88,7 @@
|
||||
|
||||
<tr>
|
||||
<td class="ruleTableHeader">
|
||||
<form:radiobutton id="radio-${technologyName}" path="technologyName" value="${technologyHolder.name}"/>
|
||||
<form:radiobutton class="technologyRadio" id="radio-${technologyName}" path="technologyName" value="${technologyHolder.name}"/>
|
||||
<b><label for="radio-${technologyName}">${technologyName}</label></b>
|
||||
</td>
|
||||
<td class="ruleTableCell" style="width:40em">
|
||||
@@ -87,14 +115,27 @@
|
||||
</table>
|
||||
|
||||
<table class="indent" style="border-spacing:3pt;">
|
||||
|
||||
<tr>
|
||||
<td><fmt:message key="playersettings.name"/></td>
|
||||
<td><form:input path="name" size="16"/></td>
|
||||
<td colspan="2"><c:import url="helpToolTip.jsp"><c:param name="topic" value="playername"/></c:import></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<tr class="technologyDepends JAVA_JUKEBOX">
|
||||
<td><fmt:message key="playersettings.javaJukeboxMixer"/></td>
|
||||
<td>
|
||||
<form:select path="javaJukeboxMixer">
|
||||
<c:forEach items="${command.javaJukeboxMixers}" var="javaJukeboxMixer">
|
||||
<form:option value="${javaJukeboxMixer}" label="${javaJukeboxMixer}"/>
|
||||
</c:forEach>
|
||||
</form:select>
|
||||
</td>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<%// Max bitrate %>
|
||||
<tr class="technologyDepends WEB EXTERNAL EXTERNAL_WITH_PLAYLIST JUKEBOX">
|
||||
<td><fmt:message key="playersettings.maxbitrate"/></td>
|
||||
<td>
|
||||
<form:select path="transcodeSchemeName" cssStyle="width:8em">
|
||||
@@ -112,11 +153,9 @@
|
||||
</c:if>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<table class="indent" style="border-spacing:3pt">
|
||||
|
||||
<table class="indent technologyDepends WEB EXTERNAL EXTERNAL_WITH_PLAYLIST JUKEBOX" style="border-spacing:3pt">
|
||||
<tr>
|
||||
<td>
|
||||
<form:checkbox path="dynamicIp" id="dynamicIp" cssClass="checkbox"/>
|
||||
@@ -143,7 +182,7 @@
|
||||
</table>
|
||||
|
||||
<c:if test="${not empty command.allTranscodings}">
|
||||
<table class="indent">
|
||||
<table class="indent technologyDepends WEB EXTERNAL EXTERNAL_WITH_PLAYLIST JUKEBOX">
|
||||
<tr><td><b><fmt:message key="playersettings.transcodings"/></b></td></tr>
|
||||
<c:forEach items="${command.allTranscodings}" var="transcoding" varStatus="loopStatus">
|
||||
<c:if test="${loopStatus.count % 3 == 1}"><tr></c:if>
|
||||
@@ -158,9 +197,8 @@
|
||||
|
||||
<input type="submit" value="<fmt:message key="common.save"/>" style="margin-top:1em;margin-right:0.3em">
|
||||
<input type="button" value="<fmt:message key="common.cancel"/>" style="margin-top:1em" onclick="location.href='nowPlaying.view'">
|
||||
</form:form>
|
||||
|
||||
</c:otherwise>
|
||||
</form:form>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
<c:if test="${settings_reload}">
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -427,3 +427,19 @@ img {
|
||||
color: black;
|
||||
}
|
||||
|
||||
/*
|
||||
* Styles related to java jukebox controls
|
||||
*/
|
||||
|
||||
.javaJukeBoxPlayerControlBarSongTime {
|
||||
width : 50px;
|
||||
font-size: 80%;
|
||||
text-align: right;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#javaJukeboxSongPositionSlider {
|
||||
width : 200px;
|
||||
height : 4px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user