Merge remote-tracking branch 'origin/pr/807'

master
Andrew DeMaria 6 years ago
commit cd05af87dc
No known key found for this signature in database
GPG Key ID: 0A3F5E91F8364EDF
  1. 10
      airsonic-main/pom.xml
  2. 4
      airsonic-main/src/main/java/org/airsonic/player/controller/SubsonicRESTController.java
  3. 6
      airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDao.java
  4. 12
      airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDaoPlayQueueFactory.java
  5. 73
      airsonic-main/src/main/java/org/airsonic/player/service/JukeboxJavaService.java
  6. 5
      airsonic-main/src/main/java/org/airsonic/player/service/JukeboxLegacySubsonicService.java
  7. 2
      airsonic-main/src/main/java/org/airsonic/player/service/TranscodingService.java
  8. 14
      airsonic-main/src/main/java/org/airsonic/player/service/jukebox/AudioPlayerFactory.java
  9. 17
      airsonic-main/src/main/java/org/airsonic/player/service/jukebox/JavaPlayerFactory.java
  10. 9
      airsonic-main/src/test/java/org/airsonic/player/TestCaseUtils.java
  11. 59
      airsonic-main/src/test/java/org/airsonic/player/api/AirsonicRestApiIntTest.java
  12. 279
      airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AbstractAirsonicRestApiJukeboxIntTest.java
  13. 38
      airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxIntTest.java
  14. 62
      airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxLegacyIntTest.java
  15. 41
      airsonic-main/src/test/java/org/airsonic/player/service/TranscodingServiceIntTest.java
  16. 13
      airsonic-main/src/test/resources/application.properties
  17. 2
      pom.xml

@ -87,6 +87,16 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId> <artifactId>spring-security-ldap</artifactId>

@ -1274,7 +1274,7 @@ public class SubsonicRESTController {
child.setSuffix(suffix); child.setSuffix(suffix);
child.setContentType(StringUtil.getMimeType(suffix)); child.setContentType(StringUtil.getMimeType(suffix));
child.setIsVideo(mediaFile.isVideo()); child.setIsVideo(mediaFile.isVideo());
child.setPath(getRelativePath(mediaFile)); child.setPath(getRelativePath(mediaFile, settingsService));
org.airsonic.player.domain.Bookmark bookmark = bookmarkCache.get(new BookmarkKey(username, mediaFile.getId())); org.airsonic.player.domain.Bookmark bookmark = bookmarkCache.get(new BookmarkKey(username, mediaFile.getId()));
if (bookmark != null) { if (bookmark != null) {
@ -1329,7 +1329,7 @@ public class SubsonicRESTController {
return null; return null;
} }
private String getRelativePath(MediaFile musicFile) { public static String getRelativePath(MediaFile musicFile, SettingsService settingsService) {
String filePath = musicFile.getPath(); String filePath = musicFile.getPath();

@ -22,6 +22,7 @@ package org.airsonic.player.dao;
import org.airsonic.player.domain.*; import org.airsonic.player.domain.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -43,6 +44,9 @@ public class PlayerDao extends AbstractDao {
"last_seen, cover_art_scheme, transcode_scheme, dynamic_ip, technology, client_id, mixer"; "last_seen, cover_art_scheme, transcode_scheme, dynamic_ip, technology, client_id, mixer";
private static final String QUERY_COLUMNS = "id, " + INSERT_COLUMNS; private static final String QUERY_COLUMNS = "id, " + INSERT_COLUMNS;
@Autowired
private PlayerDaoPlayQueueFactory playerDaoPlayQueueFactory;
private PlayerRowMapper rowMapper = new PlayerRowMapper(); private PlayerRowMapper rowMapper = new PlayerRowMapper();
private Map<Integer, PlayQueue> playlists = Collections.synchronizedMap(new HashMap<Integer, PlayQueue>()); private Map<Integer, PlayQueue> playlists = Collections.synchronizedMap(new HashMap<Integer, PlayQueue>());
@ -166,7 +170,7 @@ public class PlayerDao extends AbstractDao {
private void addPlaylist(Player player) { private void addPlaylist(Player player) {
PlayQueue playQueue = playlists.get(player.getId()); PlayQueue playQueue = playlists.get(player.getId());
if (playQueue == null) { if (playQueue == null) {
playQueue = new PlayQueue(); playQueue = playerDaoPlayQueueFactory.createPlayQueue();
playlists.put(player.getId(), playQueue); playlists.put(player.getId(), playQueue);
} }
player.setPlayQueue(playQueue); player.setPlayQueue(playQueue);

@ -0,0 +1,12 @@
package org.airsonic.player.dao;
import org.airsonic.player.domain.PlayQueue;
import org.springframework.stereotype.Component;
@Component
public class PlayerDaoPlayQueueFactory {
public PlayQueue createPlayQueue() {
return new PlayQueue();
}
}

@ -1,9 +1,9 @@
package org.airsonic.player.service; package org.airsonic.player.service;
import com.github.biconou.AudioPlayer.JavaPlayer; import com.github.biconou.AudioPlayer.api.PlayList;
import com.github.biconou.AudioPlayer.api.*; import com.github.biconou.AudioPlayer.api.PlayerListener;
import org.airsonic.player.domain.*; import org.airsonic.player.domain.*;
import org.airsonic.player.domain.Player; import org.airsonic.player.service.jukebox.JavaPlayerFactory;
import org.airsonic.player.util.FileUtil; import org.airsonic.player.util.FileUtil;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -19,23 +19,25 @@ import java.util.Map;
/** /**
* @author R??mi Cocula * @author Rémi Cocula
*/ */
@Service @Service
public class JukeboxJavaService { public class JukeboxJavaService {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(JukeboxJavaService.class); private static final org.slf4j.Logger log = LoggerFactory.getLogger(JukeboxJavaService.class);
private static final float DEFAULT_GAIN = 0.75f;
@Autowired @Autowired
private AudioScrobblerService audioScrobblerService; private AudioScrobblerService audioScrobblerService;
@Autowired @Autowired
private StatusService statusService; private StatusService statusService;
@Autowired @Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService; private SecurityService securityService;
@Autowired @Autowired
private MediaFileService mediaFileService; private MediaFileService mediaFileService;
@Autowired
private JavaPlayerFactory javaPlayerFactory;
private TransferStatus status; private TransferStatus status;
@ -45,31 +47,32 @@ public class JukeboxJavaService {
/** /**
* Finds the corresponding active audio player for a given airsonic player. * 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. * The JukeboxJavaService references all active audio players in a map indexed by airsonic player id.
* *
* @param airsonicPlayer a given airsonic player. * @param airsonicPlayer a given airsonic player.
* @return the corresponding active audio player of null if none exists. * @return the corresponding active audio player.
*/ */
private com.github.biconou.AudioPlayer.api.Player retrieveAudioPlayerForAirsonicPlayer(Player airsonicPlayer) { private com.github.biconou.AudioPlayer.api.Player retrieveAudioPlayerForAirsonicPlayer(Player airsonicPlayer) {
com.github.biconou.AudioPlayer.api.Player foundPlayer = activeAudioPlayers.get(airsonicPlayer.getId()); com.github.biconou.AudioPlayer.api.Player foundPlayer = activeAudioPlayers.get(airsonicPlayer.getId());
if (foundPlayer == null) { if (foundPlayer == null) {
synchronized (activeAudioPlayers) { synchronized (activeAudioPlayers) {
foundPlayer = initAudioPlayer(airsonicPlayer); com.github.biconou.AudioPlayer.api.Player newPlayer = initAudioPlayer(airsonicPlayer);
if (foundPlayer == null) { if (newPlayer == null) {
throw new RuntimeException("Did not initialized a player"); 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);
} }
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.get(mixer);
if (playersForMixer == null) {
playersForMixer = new ArrayList<>();
activeAudioPlayersPerMixer.put(mixer, playersForMixer);
}
playersForMixer.add(newPlayer);
foundPlayer = newPlayer;
} }
} }
return foundPlayer; return foundPlayer;
@ -88,11 +91,12 @@ public class JukeboxJavaService {
if (StringUtils.isNotBlank(airsonicPlayer.getJavaJukeboxMixer())) { if (StringUtils.isNotBlank(airsonicPlayer.getJavaJukeboxMixer())) {
log.info("use mixer : {}", airsonicPlayer.getJavaJukeboxMixer()); log.info("use mixer : {}", airsonicPlayer.getJavaJukeboxMixer());
audioPlayer = new JavaPlayer(airsonicPlayer.getJavaJukeboxMixer()); audioPlayer = javaPlayerFactory.createJavaPlayer(airsonicPlayer.getJavaJukeboxMixer());
} else { } else {
log.info("use default mixer"); log.info("use default mixer");
audioPlayer = new JavaPlayer(); audioPlayer = javaPlayerFactory.createJavaPlayer();
} }
audioPlayer.setGain(DEFAULT_GAIN);
if (audioPlayer != null) { if (audioPlayer != null) {
audioPlayer.registerListener(new PlayerListener() { audioPlayer.registerListener(new PlayerListener() {
@Override @Override
@ -159,10 +163,7 @@ public class JukeboxJavaService {
throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player"); throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player");
} }
com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer);
if (audioPlayer != null) { return audioPlayer.getGain();
return audioPlayer.getGain();
}
return 0.5f;
} }
public void setGain(final Player airsonicPlayer, final float gain) { public void setGain(final Player airsonicPlayer, final float gain) {
@ -271,20 +272,8 @@ public class JukeboxJavaService {
} }
} }
public void start(Player airsonicPlayer) throws Exception { public void start(Player airsonicPlayer) {
log.debug("begin start jukebox : player = id:{};name:{}", airsonicPlayer.getId(), airsonicPlayer.getName()); play(airsonicPlayer);
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 { public void stop(Player airsonicPlayer) throws Exception {
@ -332,7 +321,6 @@ public class JukeboxJavaService {
} }
} }
public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) { public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) {
this.audioScrobblerService = audioScrobblerService; this.audioScrobblerService = audioScrobblerService;
} }
@ -342,7 +330,6 @@ public class JukeboxJavaService {
} }
public void setSettingsService(SettingsService settingsService) { public void setSettingsService(SettingsService settingsService) {
this.settingsService = settingsService;
} }
public void setSecurityService(SecurityService securityService) { public void setSecurityService(SecurityService securityService) {

@ -21,6 +21,7 @@ package org.airsonic.player.service;
import org.airsonic.player.domain.*; import org.airsonic.player.domain.*;
import org.airsonic.player.service.jukebox.AudioPlayer; import org.airsonic.player.service.jukebox.AudioPlayer;
import org.airsonic.player.service.jukebox.AudioPlayerFactory;
import org.airsonic.player.util.FileUtil; import org.airsonic.player.util.FileUtil;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -52,6 +53,8 @@ public class JukeboxLegacySubsonicService implements AudioPlayer.Listener {
private SecurityService securityService; private SecurityService securityService;
@Autowired @Autowired
private MediaFileService mediaFileService; private MediaFileService mediaFileService;
@Autowired
private AudioPlayerFactory audioPlayerFactory;
private AudioPlayer audioPlayer; private AudioPlayer audioPlayer;
private Player player; private Player player;
@ -111,7 +114,7 @@ public class JukeboxLegacySubsonicService implements AudioPlayer.Listener {
String command = settingsService.getJukeboxCommand(); String command = settingsService.getJukeboxCommand();
parameters.setTranscoding(new Transcoding(null, null, null, null, command, null, null, false)); parameters.setTranscoding(new Transcoding(null, null, null, null, command, null, null, false));
in = transcodingService.getTranscodedInputStream(parameters); in = transcodingService.getTranscodedInputStream(parameters);
audioPlayer = new AudioPlayer(in, this); audioPlayer = audioPlayerFactory.createAudioPlayer(in, this);
audioPlayer.setGain(gain); audioPlayer.setGain(gain);
audioPlayer.play(); audioPlayer.play();
onSongStart(file); onSongStart(file);

@ -32,6 +32,7 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
@ -61,6 +62,7 @@ public class TranscodingService {
@Autowired @Autowired
private SettingsService settingsService; private SettingsService settingsService;
@Autowired @Autowired
@Lazy // used to deal with circular dependencies between PlayerService and TranscodingService
private PlayerService playerService; private PlayerService playerService;
/** /**

@ -0,0 +1,14 @@
package org.airsonic.player.service.jukebox;
import org.airsonic.player.service.JukeboxLegacySubsonicService;
import org.springframework.stereotype.Component;
import java.io.InputStream;
@Component
public class AudioPlayerFactory {
public AudioPlayer createAudioPlayer(InputStream in, JukeboxLegacySubsonicService jukeboxLegacySubsonicService) throws Exception {
return new AudioPlayer(in, jukeboxLegacySubsonicService);
}
}

@ -0,0 +1,17 @@
package org.airsonic.player.service.jukebox;
import com.github.biconou.AudioPlayer.JavaPlayer;
import com.github.biconou.AudioPlayer.api.Player;
import org.springframework.stereotype.Component;
@Component
public class JavaPlayerFactory {
public Player createJavaPlayer() {
return new JavaPlayer();
}
public Player createJavaPlayer(String mixerName) {
return new JavaPlayer(mixerName);
}
}

@ -1,5 +1,6 @@
package org.airsonic.player; package org.airsonic.player;
import org.airsonic.player.controller.JAXBWriter;
import org.airsonic.player.dao.DaoHelper; import org.airsonic.player.dao.DaoHelper;
import org.airsonic.player.service.MediaScannerService; import org.airsonic.player.service.MediaScannerService;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
@ -37,6 +38,13 @@ public class TestCaseUtils {
return airsonicHomeDirForTest.getAbsolutePath(); return airsonicHomeDirForTest.getAbsolutePath();
} }
/**
*
* @return current REST api version.
*/
public static String restApiVersion() {
return new JAXBWriter().getRestProtocolVersion();
}
/** /**
* Cleans the AIRSONIC_HOME directory used for tests. * Cleans the AIRSONIC_HOME directory used for tests.
@ -106,6 +114,7 @@ public class TestCaseUtils {
* Scans the music library * @param mediaScannerService * Scans the music library * @param mediaScannerService
*/ */
public static void execScan(MediaScannerService mediaScannerService) { public static void execScan(MediaScannerService mediaScannerService) {
// TODO create a synchronous scan
mediaScannerService.scanLibrary(); mediaScannerService.scanLibrary();
while (mediaScannerService.isScanning()) { while (mediaScannerService.isScanning()) {

@ -0,0 +1,59 @@
package org.airsonic.player.api;
import org.airsonic.player.TestCaseUtils;
import org.airsonic.player.util.HomeRule;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class AirsonicRestApiIntTest {
public static final String CLIENT_NAME = "airsonic";
public static final String AIRSONIC_USER = "admin";
public static final String AIRSONIC_PASSWORD = "admin";
public static final String EXPECTED_FORMAT = "json";
private static String AIRSONIC_API_VERSION;
@Autowired
private MockMvc mvc;
@ClassRule
public static final HomeRule classRule = new HomeRule(); // sets airsonic.home to a temporary dir
@BeforeClass
public static void setupClass() {
AIRSONIC_API_VERSION = TestCaseUtils.restApiVersion();
}
@Test
public void pingTest() throws Exception {
mvc.perform(get("/rest/ping")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.param("u", AIRSONIC_USER)
.param("p", AIRSONIC_PASSWORD)
.param("f", EXPECTED_FORMAT)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.subsonic-response.status").value("ok"))
.andExpect(jsonPath("$.subsonic-response.version").value(AIRSONIC_API_VERSION))
.andDo(print());
}
}

@ -0,0 +1,279 @@
package org.airsonic.player.api.jukebox;
import org.airsonic.player.TestCaseUtils;
import org.airsonic.player.dao.*;
import org.airsonic.player.domain.*;
import org.airsonic.player.service.MediaScannerService;
import org.airsonic.player.service.PlayerService;
import org.airsonic.player.service.SettingsService;
import org.airsonic.player.util.HomeRule;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
public abstract class AbstractAirsonicRestApiJukeboxIntTest {
@ClassRule
public static final HomeRule classRule = new HomeRule(); // sets airsonic.home to a temporary dir
@TestConfiguration
static class Config {
private static class SpiedPlayerDaoPlayQueueFactory extends PlayerDaoPlayQueueFactory {
@Override
public PlayQueue createPlayQueue() {
return spy(super.createPlayQueue());
}
}
@Bean
public PlayerDaoPlayQueueFactory playerDaoPlayQueueFactory() {
return new SpiedPlayerDaoPlayQueueFactory();
}
}
protected static final String CLIENT_NAME = "airsonic";
protected static final String JUKEBOX_PLAYER_NAME = CLIENT_NAME + "-jukebox";
private static final String EXPECTED_FORMAT = "json";
private static String AIRSONIC_API_VERSION;
private static boolean dataBasePopulated;
private static DaoHelper staticDaoHelper;
@Autowired
protected PlayerService playerService;
@Autowired
private MockMvc mvc;
@Autowired
private MusicFolderDao musicFolderDao;
@Autowired
private SettingsService settingsService;
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private PlayerDao playerDao;
@Autowired
private MediaFileDao mediaFileDao;
@Autowired
private DaoHelper daoHelper;
@Autowired
private AlbumDao albumDao;
@Autowired
private ArtistDao artistDao;
private Player testJukeboxPlayer;
@BeforeClass
public static void setupClass() {
AIRSONIC_API_VERSION = TestCaseUtils.restApiVersion();
dataBasePopulated = false;
}
@AfterClass
public static void cleanDataBase() {
staticDaoHelper.getJdbcTemplate().execute("DROP SCHEMA PUBLIC CASCADE");
staticDaoHelper = null;
dataBasePopulated = false;
}
/**
* Populate test datas in the database only once.
*
* <ul>
* <li>Creates 2 music folder</li>
* <li>Scans the music folders</li>
* <li>Creates a test jukebox player</li>
* </ul>
*/
private void populateDatabase() {
if (!dataBasePopulated) {
staticDaoHelper = daoHelper;
assertThat(musicFolderDao.getAllMusicFolders().size()).isEqualTo(1);
MusicFolderTestData.getTestMusicFolders().forEach(musicFolderDao::createMusicFolder);
settingsService.clearMusicFolderCache();
TestCaseUtils.execScan(mediaScannerService);
assertThat(playerDao.getAllPlayers().size()).isEqualTo(0);
createTestPlayer();
assertThat(playerDao.getAllPlayers().size()).isEqualTo(1);
dataBasePopulated = true;
}
}
@Before
public void setup() throws Exception {
populateDatabase();
testJukeboxPlayer = findTestJukeboxPlayer();
assertThat(testJukeboxPlayer).isNotNull();
reset(testJukeboxPlayer.getPlayQueue());
testJukeboxPlayer.getPlayQueue().clear();
assertThat(testJukeboxPlayer.getPlayQueue().size()).isEqualTo(0);
testJukeboxPlayer.getPlayQueue().addFiles(true,
mediaFileDao.getSongsForAlbum("_DIR_ Ravel", "Complete Piano Works"));
assertThat(testJukeboxPlayer.getPlayQueue().size()).isEqualTo(2);
}
protected abstract void createTestPlayer();
private Player findTestJukeboxPlayer() {
return playerDao.getAllPlayers().stream().filter(player -> player.getName().equals(JUKEBOX_PLAYER_NAME))
.findFirst().orElseThrow(() -> new RuntimeException("No player found in database"));
}
private String convertDateToString(Date date) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.000'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
return formatter.format(date);
}
private ResultMatcher playListItem1isCorrect() {
MediaFile mediaFile = testJukeboxPlayer.getPlayQueue().getFile(0);
MediaFile parent = mediaFileDao.getMediaFile(mediaFile.getParentPath());
Album album = albumDao.getAlbum(mediaFile.getArtist(), mediaFile.getAlbumName());
Artist artist = artistDao.getArtist(mediaFile.getArtist());
assertThat(album).isNotNull();
return result -> {
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].id").value(mediaFile.getId()).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].parent").value(parent.getId()).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].isDir").value(false).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].title").value("01 - Gaspard de la Nuit - i. Ondine").match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].album").value("Complete Piano Works").match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].artist").value("_DIR_ Ravel").match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].coverArt").value(parent.getId()).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].size").value(45138).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].contentType").value("audio/mpeg").match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].suffix").value("mp3").match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].duration").value(2).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].bitRate").value(128).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].path").value("_DIR_ Ravel/_DIR_ Ravel - Complete Piano Works/01 - Gaspard de la Nuit - i. Ondine.mp3").match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].isVideo").value(false).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].playCount").isNumber().match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].created").value(convertDateToString(mediaFile.getCreated())).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].albumId").value(album.getId()).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].artistId").value(artist.getId()).match(result);
jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].type").value("music").match(result);
};
}
@Test
@WithMockUser(username = "admin")
public void jukeboxStartActionTest() throws Exception {
// Given
// When and Then
performStartAction();
performStatusAction("true");
performGetAction()
.andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.currentIndex").value("0"))
.andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.playing").value("true"))
.andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.gain").value("0.75"))
.andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.position").value("0"))
.andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.entry").isArray())
.andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.entry.length()").value(2))
.andExpect(playListItem1isCorrect())
.andDo(print());
verify(testJukeboxPlayer.getPlayQueue(), times(2)).setStatus(PlayQueue.Status.PLAYING);
assertThat(testJukeboxPlayer.getPlayQueue().getIndex()).isEqualTo(0);
assertThat(testJukeboxPlayer.getPlayQueue().getStatus()).isEqualTo(PlayQueue.Status.PLAYING);
}
@Test
@WithMockUser(username = "admin")
public void jukeboxStopActionTest() throws Exception {
// Given
// When and Then
performStartAction();
performStatusAction("true");
performStopAction();
performStatusAction("false");
verify(testJukeboxPlayer.getPlayQueue(), times(2)).setStatus(PlayQueue.Status.PLAYING);
verify(testJukeboxPlayer.getPlayQueue(), times(1)).setStatus(PlayQueue.Status.STOPPED);
assertThat(testJukeboxPlayer.getPlayQueue().getIndex()).isEqualTo(0);
assertThat(testJukeboxPlayer.getPlayQueue().getStatus()).isEqualTo(PlayQueue.Status.STOPPED);
}
private void performStatusAction(String expectedPlayingValue) throws Exception {
mvc.perform(get("/rest/jukeboxControl.view")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.param("f", EXPECTED_FORMAT)
.param("action", "status")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.subsonic-response.status").value("ok"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.currentIndex").value("0"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.playing").value(expectedPlayingValue))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.position").value("0"));
}
private ResultActions performGetAction() throws Exception {
return mvc.perform(get("/rest/jukeboxControl.view")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.param("f", EXPECTED_FORMAT)
.param("action", "get")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.subsonic-response.status").value("ok"));
}
private void performStopAction() throws Exception {
mvc.perform(get("/rest/jukeboxControl.view")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.param("f", EXPECTED_FORMAT)
.param("action", "stop")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.subsonic-response.status").value("ok"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.currentIndex").value("0"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.playing").value("false"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.position").value("0"));
}
private void performStartAction() throws Exception {
mvc.perform(get("/rest/jukeboxControl.view")
.param("v", AIRSONIC_API_VERSION)
.param("c", CLIENT_NAME)
.param("f", EXPECTED_FORMAT)
.param("action", "start")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.subsonic-response.status").value("ok"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.currentIndex").value("0"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.playing").value("true"))
.andExpect(jsonPath("$.subsonic-response.jukeboxStatus.position").value("0"));
}
}

@ -0,0 +1,38 @@
package org.airsonic.player.api.jukebox;
import com.github.biconou.AudioPlayer.JavaPlayer;
import org.airsonic.player.domain.Player;
import org.airsonic.player.domain.PlayerTechnology;
import org.airsonic.player.service.jukebox.JavaPlayerFactory;
import org.junit.Before;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class AirsonicRestApiJukeboxIntTest extends AbstractAirsonicRestApiJukeboxIntTest {
@MockBean
protected JavaPlayerFactory javaPlayerFactory;
@Before
@Override
public void setup() throws Exception {
super.setup();
JavaPlayer mockJavaPlayer = mock(JavaPlayer.class);
when(mockJavaPlayer.getPlayingInfos()).thenReturn( () -> 0 );
when(mockJavaPlayer.getGain()).thenReturn(0.75f);
when(javaPlayerFactory.createJavaPlayer()).thenReturn(mockJavaPlayer);
}
@Override
protected void createTestPlayer() {
Player jukeBoxPlayer = new Player();
jukeBoxPlayer.setName(JUKEBOX_PLAYER_NAME);
jukeBoxPlayer.setUsername("admin");
jukeBoxPlayer.setClientId(CLIENT_NAME + "-jukebox");
jukeBoxPlayer.setTechnology(PlayerTechnology.JAVA_JUKEBOX);
playerService.createPlayer(jukeBoxPlayer);
}
}

@ -0,0 +1,62 @@
package org.airsonic.player.api.jukebox;
import org.airsonic.player.domain.Player;
import org.airsonic.player.domain.PlayerTechnology;
import org.airsonic.player.service.TranscodingService;
import org.airsonic.player.service.jukebox.AudioPlayer;
import org.airsonic.player.service.jukebox.AudioPlayerFactory;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.security.test.context.support.WithMockUser;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
public class AirsonicRestApiJukeboxLegacyIntTest extends AirsonicRestApiJukeboxIntTest {
@SpyBean
private TranscodingService transcodingService;
@MockBean
protected AudioPlayerFactory audioPlayerFactory;
private AudioPlayer mockAudioPlayer;
@Before
@Override
public void setup() throws Exception {
super.setup();
mockAudioPlayer = mock(AudioPlayer.class);
when(audioPlayerFactory.createAudioPlayer(any(), any())).thenReturn(mockAudioPlayer);
doReturn(null).when(transcodingService).getTranscodedInputStream(any());
}
@Override
protected final void createTestPlayer() {
Player jukeBoxPlayer = new Player();
jukeBoxPlayer.setName(JUKEBOX_PLAYER_NAME);
jukeBoxPlayer.setUsername("admin");
jukeBoxPlayer.setClientId(CLIENT_NAME + "-jukebox");
jukeBoxPlayer.setTechnology(PlayerTechnology.JUKEBOX);
playerService.createPlayer(jukeBoxPlayer);
}
@Test
@WithMockUser(username = "admin")
@Override
public void jukeboxStartActionTest() throws Exception {
super.jukeboxStartActionTest();
verify(mockAudioPlayer).play();
}
@Test
@WithMockUser(username = "admin")
@Override
public void jukeboxStopActionTest() throws Exception {
super.jukeboxStopActionTest();
verify(mockAudioPlayer).play();
verify(mockAudioPlayer).pause();
}
}

@ -0,0 +1,41 @@
package org.airsonic.player.service;
import org.airsonic.player.domain.Transcoding;
import org.airsonic.player.util.HomeRule;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.junit4.SpringRunner;
import static org.mockito.Mockito.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TranscodingServiceIntTest {
@Autowired
private TranscodingService transcodingService;
@SpyBean
private PlayerService playerService;
@ClassRule
public static final HomeRule classRule = new HomeRule(); // sets airsonic.home to a temporary dir
@Test
public void createTranscodingTest() {
// Given
Transcoding transcoding = new Transcoding(null,
"test-transcoding",
"mp3",
"wav",
"step1",
"step2",
"step3",
true);
transcodingService.createTranscoding(transcoding);
verify(playerService).getAllPlayers();
}
}

@ -0,0 +1,13 @@
spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp
server.error.includeStacktrace: ALWAYS
logging.level.root=WARN
logging.level.org.airsonic=INFO
logging.level.liquibase=INFO
logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p){green} %clr(---){faint} %clr(%-40.40logger{32}){blue} %clr(:){faint} %m%n%wEx
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} %5p --- %-40.40logger{32} : %m%n%wEx
DatabaseConfigType=embed
DatabaseConfigEmbedDriver=org.hsqldb.jdbcDriver
DatabaseConfigEmbedUrl=jdbc:hsqldb:mem:airsonic
DatabaseConfigEmbedUsername=sa
DatabaseConfigEmbedPassword=

@ -255,6 +255,8 @@
<ignoredUsedUndeclaredDependency>org.springframework:*</ignoredUsedUndeclaredDependency> <ignoredUsedUndeclaredDependency>org.springframework:*</ignoredUsedUndeclaredDependency>
<ignoredUsedUndeclaredDependency>org.springframework.security:*</ignoredUsedUndeclaredDependency> <ignoredUsedUndeclaredDependency>org.springframework.security:*</ignoredUsedUndeclaredDependency>
<ignoredUsedUndeclaredDependency>org.springframework.boot:*</ignoredUsedUndeclaredDependency> <ignoredUsedUndeclaredDependency>org.springframework.boot:*</ignoredUsedUndeclaredDependency>
<ignoredUsedUndeclaredDependency>org.assertj:*</ignoredUsedUndeclaredDependency>
<ignoredUsedUndeclaredDependency>org.hamcrest:*</ignoredUsedUndeclaredDependency>
<ignoredUsedUndeclaredDependency>org.apache.tomcat.embed:tomcat-embed-core*</ignoredUsedUndeclaredDependency> <ignoredUsedUndeclaredDependency>org.apache.tomcat.embed:tomcat-embed-core*</ignoredUsedUndeclaredDependency>
<ignoredUsedUndeclaredDependency>org.apache.tomcat:tomcat-annotations-api:*</ignoredUsedUndeclaredDependency> <ignoredUsedUndeclaredDependency>org.apache.tomcat:tomcat-annotations-api:*</ignoredUsedUndeclaredDependency>
</ignoredUsedUndeclaredDependencies> </ignoredUsedUndeclaredDependencies>

Loading…
Cancel
Save