My fork of airsonic with experimental fixes and improvements. See branch "custom"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
airsonic-custom/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxJavaService.java

313 lines
13 KiB

package org.airsonic.player.service;
import com.github.biconou.AudioPlayer.api.PlayList;
import com.github.biconou.AudioPlayer.api.PlayerListener;
import org.airsonic.player.domain.*;
import org.airsonic.player.service.jukebox.JavaPlayerFactory;
import org.airsonic.player.util.FileUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.*;
/**
* @author Rémi Cocula
*/
@Service
public class JukeboxJavaService {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(JukeboxJavaService.class);
private static final float DEFAULT_GAIN = 0.75f;
private AudioScrobblerService audioScrobblerService;
private StatusService statusService;
private SecurityService securityService;
private MediaFileService mediaFileService;
private JavaPlayerFactory javaPlayerFactory;
private TransferStatus status;
private Map<Integer, com.github.biconou.AudioPlayer.api.Player> activeAudioPlayers = new HashMap<>();
private Map<String, List<com.github.biconou.AudioPlayer.api.Player>> activeAudioPlayersPerMixer = new HashMap<>();
private final static String DEFAULT_MIXER_ENTRY_KEY = "_default";
public JukeboxJavaService(AudioScrobblerService audioScrobblerService,
StatusService statusService,
SecurityService securityService,
MediaFileService mediaFileService,
JavaPlayerFactory javaPlayerFactory) {
this.audioScrobblerService = audioScrobblerService;
this.statusService = statusService;
this.securityService = securityService;
this.mediaFileService = mediaFileService;
this.javaPlayerFactory = javaPlayerFactory;
}
/**
* Finds the corresponding active audio player for a given airsonic player.
* If no player exists we create one.
* 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.
*/
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) {
com.github.biconou.AudioPlayer.api.Player newPlayer = initAudioPlayer(airsonicPlayer);
activeAudioPlayers.put(airsonicPlayer.getId(), newPlayer);
String mixer = airsonicPlayer.getJavaJukeboxMixer();
if (StringUtils.isBlank(mixer)) {
mixer = DEFAULT_MIXER_ENTRY_KEY;
}
List<com.github.biconou.AudioPlayer.api.Player> playersForMixer = activeAudioPlayersPerMixer.computeIfAbsent(mixer, k -> new ArrayList<>());
playersForMixer.add(newPlayer);
foundPlayer = newPlayer;
}
}
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 = javaPlayerFactory.createJavaPlayer(airsonicPlayer.getJavaJukeboxMixer());
} else {
log.info("use default mixer");
audioPlayer = javaPlayerFactory.createJavaPlayer();
}
if (audioPlayer != null) {
audioPlayer.setGain(DEFAULT_GAIN);
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);
return audioPlayer.getGain();
}
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.
*/
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() {
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();
}
});
synchronized (activeAudioPlayers) {
// 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) {
play(airsonicPlayer);
}
public void stop(Player airsonicPlayer) {
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();
}
public void skip(Player airsonicPlayer, int index, int offset) {
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);
}
}
}
}