/****************************************************************************************
* Copyright (c) 2009 Edu Zamora <edu.zasu@gmail.com> *
* *
* This program 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. *
* *
* This program 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 *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki.service;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.client.utils.URIUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ichi2.anki.Utils;
/**
* Class used to parse, load and play sound files on AnkiDroid.
*/
public class Sound {
public static Logger log = LoggerFactory.getLogger(Sound.class);
/**
* Pattern used to identify the markers for sound files
*/
public static Pattern sSoundPattern = Pattern.compile("\\[sound\\:([^\\[\\]]*)\\]");
/**
* Media player used to play the sounds
*/
private static MediaPlayer sMediaPlayer;
/**
* ArrayList to store the current sound paths
*/
private static ArrayList<String> sSoundPaths;
/**
* Counter of the number of sounds played out of the total number of sounds in soundPaths
*/
private static int sNumSoundsPlayed;
/* Prevent class from being instantiated */
private Sound() { }
public static String parseSounds(String soundDir, String content, boolean ttsEnabled, int qa) {
boolean soundAvailable = false;
StringBuilder stringBuilder = new StringBuilder();
String contentLeft = content;
log.info("parseSounds");
sSoundPaths = new ArrayList<String>();
Matcher matcher = sSoundPattern.matcher(content);
// While there is matches of the pattern for sound markers
while (matcher.find()) {
soundAvailable = true;
// Get the sound file name
String sound = matcher.group(1);
// Construct the sound path and store it
String soundPath = soundDir + URLEncoder.encode(sound, "UTF-8");
sSoundPaths.add(soundPath);
// Construct the new content, appending the substring from the beginning of the content left until the
// beginning of the sound marker
// and then appending the html code to add the play button
String soundMarker = matcher.group();
int markerStart = contentLeft.indexOf(soundMarker);
stringBuilder.append(contentLeft.substring(0, markerStart));
stringBuilder
.append("<a onclick=\"window.interface.playSound(this.title);\" title=\""
+ soundPath
+ "\"><span style=\"padding:5px;display:inline-block;vertical-align:middle\"><img src=\"file:///android_asset/media_playback_start2.png\" /></span></a>");
contentLeft = contentLeft.substring(markerStart + soundMarker.length());
log.info("Content left = " + contentLeft);
}
if (!soundAvailable && ttsEnabled && !ReadText.getLanguage(qa).equals(ReadText.NO_TTS)) {
stringBuilder.append(content.substring(0, content.length() - 9));
stringBuilder
.append("<a onclick=\"window.interface.playSound(this.title);\" title=\"tts" + Integer.toString(qa)
+ Utils.stripHTML(content)
+ "\"><span style=\"padding:5px;display:inline-block;vertical-align:middle\"><img src=\"file:///android_asset/media_playback_start2.png\" /></span></a>");
contentLeft = "</p>";
}
stringBuilder.append(contentLeft);
return stringBuilder.toString();
}
/**
* Plays the sounds stored on the paths indicated by mSoundPaths.
*/
public static void playSounds(String text, int qa) {
// If there are sounds to play for the current card, play the first one
if (sSoundPaths != null && sSoundPaths.size() > 0) {
sNumSoundsPlayed = 0;
playSound(sNumSoundsPlayed);
} else if (text != null && Integer.valueOf(android.os.Build.VERSION.SDK) > 3) {
ReadText.textToSpeech(text, qa);
}
}
/**
* Play the sound indicated by the path stored on the position soundToPlayIndex of the mSoundPaths array.
*
* @param soundToPlayIndex
*/
private static void playSound(int soundToPlayIndex) {
playSound(sSoundPaths.get(soundToPlayIndex), true);
}
public static void playSound(String soundPath, boolean playAll) {
if (soundPath.substring(0, 3).equals("tts")) {
ReadText.textToSpeech(soundPath.substring(4, soundPath.length()), Integer.parseInt(soundPath.substring(3, 4)));
} else if (sSoundPaths.contains(soundPath)) {
sMediaPlayer = new MediaPlayer();
try {
// soundPath is usually an URI, but Media player requires a path not url encoded
URI soundURI = new URI(soundPath);
soundPath = new File(soundURI).getAbsolutePath();
sMediaPlayer.setDataSource(soundPath);
sMediaPlayer.setVolume(AudioManager.STREAM_MUSIC, AudioManager.STREAM_MUSIC);
sMediaPlayer.prepare();
if (playAll) {
sMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
releaseSound();
sNumSoundsPlayed++;
// If there is still more sounds to play for the current card, play the next one
if (sNumSoundsPlayed < sSoundPaths.size()) {
playSound(sNumSoundsPlayed);
}
}
});
}
sMediaPlayer.start();
} catch (Exception e) {
log.error("playSounds - Error reproducing sound " + soundPath + " = " + e.getMessage());
releaseSound();
}
}
}
/**
* Releases the sound.
*/
private static void releaseSound() {
if (sMediaPlayer != null) {
sMediaPlayer.release();
sMediaPlayer = null;
}
}
/**
* Stops the playing sounds.
*/
public static void stopSounds() {
if (sMediaPlayer != null) {
sMediaPlayer.stop();
releaseSound();
}
if (Integer.valueOf(android.os.Build.VERSION.SDK) > 3) {
ReadText.stopTts();
}
}
}