/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox 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 any later version. Jukefox 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 * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.jukefox.controller.player.playbackcontroller; import java.util.Timer; import java.util.TimerTask; import android.util.FloatMath; import ch.ethz.dcg.jukefox.commons.DataUnavailableException; import ch.ethz.dcg.jukefox.commons.utils.Log; import ch.ethz.dcg.jukefox.controller.player.IPlaybackInfoBroadcaster; import ch.ethz.dcg.jukefox.controller.player.mediaplayer.IMediaPlayerWrapper; import ch.ethz.dcg.jukefox.controller.player.playlistmanager.IPlaylistManager; import ch.ethz.dcg.jukefox.model.AbstractCollectionModelManager; import ch.ethz.dcg.jukefox.model.AbstractPlayerModelManager; import ch.ethz.dcg.jukefox.model.collection.BaseAlbum; import ch.ethz.dcg.jukefox.model.collection.BaseArtist; import ch.ethz.dcg.jukefox.model.collection.PlaylistSong; import ch.ethz.dcg.jukefox.model.commons.NoNextSongException; import ch.ethz.dcg.jukefox.model.player.PlayerState; import ch.ethz.dcg.jukefox.playmode.PlayerControllerCommands; public class CrossfadingPlaybackController extends DurationBugfixPlaybackController { public static final String TAG = CrossfadingPlaybackController.class.getSimpleName(); private int crossfadingTime = 6000; private float maxVolume = 0.9f; public CrossfadingPlaybackController(IPlaybackInfoBroadcaster listenerInformer, AbstractCollectionModelManager collectionModel, AbstractPlayerModelManager playerModel, IPlaylistManager currentPlaylistManager, int autoGapRemoveTime, int manualgapRemoveTime, IMediaPlayerWrapper mediaPlayer1, IMediaPlayerWrapper mediaPlayer2, boolean beatMatching) { super(listenerInformer, collectionModel, playerModel, currentPlaylistManager, autoGapRemoveTime, manualgapRemoveTime, mediaPlayer1, mediaPlayer2); Log.v(TAG, "CrossfadingPlaybackController created!"); } @Override protected synchronized void setNewPrepareTimer() { // Log.v(TAG, "cancelTimers() setNewPrepareTimer()"); cancelTimers(); if (getState() != PlayerState.PLAY) { return; } IMediaPlayerWrapper currentMediaPlayer = getCurrentMediaPlayer(); prepareTimer = new Timer(); int crossfadeIn = getDuration() - getCurrentPosition(currentMediaPlayer) - gaplessSongPrepareOffset - getSongFadeoutOffset() - crossfadingTime; if (crossfadeIn <= crossfadingTime) { prepareNextSongToPlay(); return; } prepareTimer.schedule(new TimerTask() { @Override public void run() { prepareNextSongToPlay(); } }, crossfadeIn); } private int getSongFadeoutOffset() { return getDuration() / 20; } private int getSongFadeinOffset() { return getDuration() / 20; } @Override protected synchronized void setNewPrepareTimer(int position) { cancelTimers(); if (getState() != PlayerState.PLAY) { return; } prepareTimer = new Timer(); int prepareIn = getDuration() - position - gaplessSongPrepareOffset - getSongFadeoutOffset() - crossfadingTime; if (prepareIn <= crossfadingTime) { prepareNextSongToPlay(); return; } prepareTimer.schedule(new TimerTask() { @Override public void run() { prepareNextSongToPlay(); } }, prepareIn); } private synchronized void prepareNextSongToPlay() { setNewPlayTimer(); preloadNextSong(); // Log.v(TAG, "Play timer set"); } @Override protected synchronized void setNewPlayTimer() { // Log.v(TAG, "cancelTimers() in setNewPlayTimers()"); cancelTimers(); if (getState() != PlayerState.PLAY) { // Log.v(TAG, "Player not in play mode. Not setting play timer."); return; } playTimer = new Timer(); TimerTask playPreparedSongTask = new TimerTask() { @Override public void run() { playPreloadedSong(); } }; IMediaPlayerWrapper currentMediaPlayer = getCurrentMediaPlayer(); int playIn = getDuration() - getCurrentPosition(currentMediaPlayer) - manualGapRemoveTime - gaplessTimeMeasures.getGapTime() - getSongFadeoutOffset() - crossfadingTime; if (playIn < 0) { playPreloadedSong(); return; } playTimer.schedule(playPreparedSongTask, playIn); // Log.v(TAG, "Set play timer"); } @Override protected synchronized void playPreloadedSong() { if (!isNextSongPrepared) { try { lastCommands = currentPlaylistManager.getPlayMode().next(currentPlaylistManager.getCurrentPlaylist()); PlaylistSong<BaseArtist, BaseAlbum> songToLoad = applyPreloadControlCommands(lastCommands); Log.v(TAG, "preloadNextSong() " + songToLoad.getArtist().getName() + " - " + songToLoad.getName()); try { isNextSongPrepared = loadSongIntoPlayer(songToLoad, collectionModel.getOtherDataProvider() .getSongPath(songToLoad), getCurrentMediaPlayer()); lastPreloadedSongId = songToLoad.getId(); } catch (DataUnavailableException e) { Log.w(TAG, e); } applyPlayPreloadedControlCommands(lastCommands); } catch (NoNextSongException e) { Log.w(TAG, e); stop(); } return; } startCrossfading(); //IMediaPlayerWrapper currentMP = switchMediaPlayer(); //play(currentMP); try { applyPlayPreloadedControlCommands(lastCommands); } catch (Exception e) { } seekTo(getSongFadeinOffset()); setMeasureTimer(); lastPlayedSongId = lastPreloadedSongId; Log.i(TAG, "started " + System.currentTimeMillis() + " " + currentMediaPlayerId); isNextSongPrepared = false; setNewPrepareTimer(); } private void startCrossfading() { final IMediaPlayerWrapper currentMP = switchMediaPlayer(); IMediaPlayerWrapper tempMP = mediaPlayer; if (tempMP == currentMP) { tempMP = mediaPlayer2; } final IMediaPlayerWrapper otherMP = tempMP; currentMP.setVolume(0.0f, 0.0f); otherMP.setVolume(maxVolume, maxVolume); Thread crossfadingThread = new Thread(new Runnable() { @Override public void run() { int elapsedTime = 0; int i = (int) (Math.random() * 4 + 0.49); Log.i(TAG, "crossfading mode " + i); long startTime = System.currentTimeMillis(); //play(otherMP); while (elapsedTime < crossfadingTime) { elapsedTime = (int) (System.currentTimeMillis() - startTime); float currentVolume1 = getVolume((float) elapsedTime / crossfadingTime, i) * maxVolume; float currentVolume2 = getVolume(1f - (float) elapsedTime / crossfadingTime, i) * maxVolume; currentMP.setVolume(currentVolume1, currentVolume1); //otherMP.setVolume(maxVolume - currentVolume, maxVolume - currentVolume); otherMP.setVolume(currentVolume2, currentVolume2); try { Thread.sleep(40); } catch (InterruptedException e) { Log.w(TAG, e); } } } }); crossfadingThread.start(); } private float getVolume(float time, int function) { switch (function) { case 0: return FloatMath.sqrt(time); case 1: return time; case 2: return time * time; case 3: return time * time * time; case 4: return FloatMath.sqrt(time) * FloatMath.sqrt(time); } return 1; } @Override protected synchronized void preloadNextSong() { gaplessTimeMeasures.setSong1Times(System.currentTimeMillis(), getPlaybackPosition(), getDuration()); if (isNextSongPrepared) { return; } // Log.v(TAG, "preloadNextSong"); IMediaPlayerWrapper nextMP = getNextMediaPlayer(); PlaylistSong<BaseArtist, BaseAlbum> songToLoad; try { PlayerControllerCommands commands = currentPlaylistManager.getPlayMode().next( currentPlaylistManager.getCurrentPlaylist()); songToLoad = applyPreloadControlCommands(commands); lastCommands = commands; Log.v(TAG, "preloadNextSong() " + songToLoad.getArtist().getName() + " - " + songToLoad.getName()); try { isNextSongPrepared = loadSongIntoPlayer(songToLoad, collectionModel.getOtherDataProvider().getSongPath( songToLoad), nextMP); lastPreloadedSongId = songToLoad.getId(); } catch (DataUnavailableException e) { Log.w(TAG, e); } } catch (NoNextSongException e) { // Log.w(TAG, e); } } }