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