/*
* 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.io.File;
import java.util.Timer;
import java.util.TimerTask;
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.mediaplayer.OnMediaPlayerEventListener;
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;
import entagged.audioformats.AudioFile;
import entagged.audioformats.AudioFileIO;
/**
* Class to fix the bug of Android 2.3 that return wrong durations of certain file formats. This lead to a malfuctioning
* gapless player. This Class does work around this by not offering gapless playback for these files.
*
* @author saaam
*
*/
public class DurationBugfixPlaybackController extends GaplessPlaybackController {
public static final String TAG = DurationBugfixPlaybackController.class.getSimpleName();
private int durationOfSongInPlayer1 = 0;
private int durationOfSongInPlayer2 = 0;
public DurationBugfixPlaybackController(IPlaybackInfoBroadcaster listenerInformer,
AbstractCollectionModelManager collectionModel, AbstractPlayerModelManager playerModel,
IPlaylistManager currentPlaylistManager, int autoGapRemoveTime, int manualgapRemoveTime,
IMediaPlayerWrapper mediaPlayer1, IMediaPlayerWrapper mediaPlayer2) {
super(listenerInformer, collectionModel, playerModel, currentPlaylistManager, autoGapRemoveTime,
manualgapRemoveTime, mediaPlayer1, mediaPlayer2);
mediaPlayer1.setOnMediaPlayerEventListener(new OnMediaPlayerEventListener() {
@Override
public void onSongCompleted(IMediaPlayerWrapper mediaPlayer) {
DurationBugfixPlaybackController.this.onSongCompleted(mediaPlayer.getCurrentSong());
}
});
mediaPlayer2.setOnMediaPlayerEventListener(new OnMediaPlayerEventListener() {
@Override
public void onSongCompleted(IMediaPlayerWrapper mediaPlayer) {
DurationBugfixPlaybackController.this.onSongCompleted(mediaPlayer.getCurrentSong());
}
});
}
@Override
protected boolean loadSongIntoPlayer(PlaylistSong<BaseArtist, BaseAlbum> song, String path,
IMediaPlayerWrapper currentMP) {
boolean success = super.loadSongIntoPlayer(song, path, currentMP);
int duration = readSongDurationWithTagLibrary(lastSongPath);
if (currentMP == mediaPlayer) {
durationOfSongInPlayer1 = duration;
} else {
durationOfSongInPlayer2 = duration;
}
return success;
}
@Override
public synchronized int getDuration() {
int duration = -1;
if (currentMediaPlayerId == 1) {
duration = durationOfSongInPlayer1;
} else if (currentMediaPlayerId == 2) {
duration = durationOfSongInPlayer2;
}
if (duration > 0) {
return duration;
}
return super.getDuration();
}
@Override
protected synchronized void setNewPrepareTimer() {
// Log.v(TAG, "cancelTimers() setNewPrepareTimer()");
cancelTimers();
if (getState() != PlayerState.PLAY) {
// Log.v(TAG,
// "Player not in play mode. Not setting prepare timer.");
return;
}
IMediaPlayerWrapper currentMediaPlayer = getCurrentMediaPlayer();
prepareTimer = new Timer();
int prepareIn = getDuration() - getCurrentPosition(currentMediaPlayer) - gaplessSongPrepareOffset;
if (prepareIn <= 0) {
prepareNextSongToPlay();
return;
}
prepareTimer.schedule(new TimerTask() {
@Override
public void run() {
prepareNextSongToPlay();
}
}, prepareIn);
}
protected synchronized void setNewPrepareTimer(int position) {
cancelTimers();
if (getState() != PlayerState.PLAY) {
return;
}
prepareTimer = new Timer();
int prepareIn = getDuration() - position - gaplessSongPrepareOffset;
if (prepareIn <= 0) {
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();
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;
}
IMediaPlayerWrapper currentMP = switchMediaPlayer();
// play(currentMP);
try {
applyPlayPreloadedControlCommands(lastCommands);
} catch (Exception e) {
}
setMeasureTimer();
lastPlayedSongId = lastPreloadedSongId;
Log.i(TAG, "started " + System.currentTimeMillis() + " " + currentMediaPlayerId);
isNextSongPrepared = false;
setNewPrepareTimer();
}
@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);
}
}
@Override
public synchronized void seekTo(int position) {
// Log.v(TAG, "cancelTimers() in seekTo()");
cancelTimers();
seekTo(getCurrentMediaPlayer(), position);
setNewPrepareTimer(position);
}
// @Override
// protected void seekTo(AndroidMediaPlayerWrapper mp, int position) {
// if (getState() != PlayerState.PAUSE && getState() != PlayerState.PLAY) {
// rememberSeekToPosition(position);
// if (getState() == PlayerState.STOP) {
// setPlayerState(PlayerState.PAUSE);
// }
// }
// try {
// int realDuration = getDuration();
// int mpDuration = mp.getDuration();
// position = Math.min(realDuration, position);
// position = Math.max(0, position);
// Log.v(TAG, "seek to pos: " + position);
// long tempPosition = position;
// if (realDuration != mpDuration) {
// tempPosition = tempPosition * mpDuration / realDuration;
// }
// position = (int) tempPosition;
// Log.v(TAG, "seek to pos2: " + position);
// mp.seekTo(position);
// } catch (Exception e) {
// Log.w(TAG, e);
// }
// }
/**
* returns the song duration in milliseconds or -1 if it is not able to read the duration
*
* @param path
* @return
*/
private int readSongDurationWithTagLibrary(String path) {
float exactDuration = -1;
try {
// Log.v("Tag Reader", "Reading tags from " + song.getName());
File f = new File(path);
AudioFile af = AudioFileIO.read(f);
exactDuration = af.getPreciseLength();
int duration = (int) Math.floor(exactDuration * 1000);
Log.v(TAG, "Read duration from tag: " + duration + "ms");
return duration;
} catch (Exception e) {
Log.w(TAG, e);
return -1;
}
}
}