Fix loading sound files. Fixes #819

This commit is contained in:
Phillip Thelen 2017-10-30 19:11:54 +01:00
parent c45b0d2516
commit d48a66be00
8 changed files with 223 additions and 263 deletions

View file

@ -1,81 +0,0 @@
package com.habitrpg.android.habitica.helpers;
import android.media.AudioManager;
import android.media.MediaPlayer;
import java.io.File;
public class SoundFile implements MediaPlayer.OnCompletionListener {
private String theme;
private String fileName;
private File file;
private MediaPlayer mp;
private Boolean playerPrepared = false;
private boolean isPlaying;
public SoundFile(String theme, String fileName) {
this.theme = theme;
this.fileName = fileName;
mp = new MediaPlayer();
mp.setOnCompletionListener(this);
}
public String getTheme() {
return theme;
}
public String getFileName() {
return fileName;
}
public String getWebUrl() {
return "https://habitica.com/assets/audio/" + getTheme() + "/" + getFileName() + ".mp3";
}
public String getFilePath() {
return getTheme() + "_" + getFileName() + ".mp3";
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public void prepareMediaPlayer() {
if (playerPrepared) {
return;
}
String path = file.getPath();
try {
mp.setDataSource(path);
mp.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
mp.prepare();
playerPrepared = true;
file = null;
} catch (Exception e) {
e.printStackTrace();
}
}
public void play() {
if (isPlaying) {
return;
}
prepareMediaPlayer();
isPlaying = true;
mp.start();
}
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
isPlaying = false;
}
}

View file

@ -0,0 +1,55 @@
package com.habitrpg.android.habitica.helpers
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.Build
import java.io.File
class SoundFile(val theme: String, private val fileName: String) : MediaPlayer.OnCompletionListener {
var file: File? = null
private var playerPrepared: Boolean = false
private var isPlaying: Boolean = false
val webUrl: String
get() = "https://habitica.com/static/audio/$theme/$fileName.mp3"
val filePath: String
get() = theme + "_" + fileName + ".mp3"
init {
}
fun play(context: Context) {
if (isPlaying) {
return
}
val m = MediaPlayer()
m.setOnCompletionListener { mp ->
isPlaying = false
mp.release()
}
try {
m.setDataSource(file?.path)
m.prepare()
playerPrepared = true
m.setVolume(100f, 100f)
m.isLooping = false
isPlaying = true
m.start()
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onCompletion(mediaPlayer: MediaPlayer) {
isPlaying = false
}
}

View file

@ -1,91 +0,0 @@
package com.habitrpg.android.habitica.helpers;
import android.content.Context;
import android.support.annotation.Nullable;
import com.habitrpg.android.habitica.HabiticaApplication;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.BufferedSink;
import okio.Okio;
import rx.Observable;
import rx.exceptions.OnErrorThrowable;
import rx.schedulers.Schedulers;
// based on http://stackoverflow.com/questions/29838565/downloading-files-using-okhttp-okio-and-rxjava
public class SoundFileLoader {
private final Context context;
private OkHttpClient client;
public SoundFileLoader(Context context) {
client = new OkHttpClient();
this.context = context;
}
public Observable<List<SoundFile>> download(List<SoundFile> files) {
return Observable.from(files)
.flatMap(audioFile -> {
File file = new File(getFullAudioFilePath(audioFile));
if (file.exists()) {
// Important, or else the MediaPlayer can't access this file
file.setReadable(true, false);
audioFile.setFile(file);
return Observable.just(audioFile);
}
final Observable<SoundFile> fileObservable = Observable.create(sub -> {
Request request = new Request.Builder().url(audioFile.getWebUrl()).build();
Response response;
try {
response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException();
}
} catch (IOException io) {
throw OnErrorThrowable.from(OnErrorThrowable.addValueAsLastCause(io, audioFile));
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
try {
BufferedSink sink = Okio.buffer(Okio.sink(file));
sink.writeAll(response.body().source());
sink.flush();
sink.close();
} catch (IOException io) {
throw OnErrorThrowable.from(OnErrorThrowable.addValueAsLastCause(io, audioFile));
}
file.setReadable(true, false);
audioFile.setFile(file);
sub.onNext(audioFile);
sub.onCompleted();
}
});
return fileObservable.subscribeOn(Schedulers.io());
}, 5)
.toList()
.map(ArrayList::new);
}
@Nullable
private String getExternalCacheDir() {
File cacheDir = HabiticaApplication.getInstance(context).getExternalCacheDir();
if (cacheDir != null) {
return cacheDir.getPath();
} else {
return null;
}
}
public String getFullAudioFilePath(SoundFile soundFile) {
return getExternalCacheDir() + File.separator + soundFile.getFilePath();
}
}

View file

@ -0,0 +1,82 @@
package com.habitrpg.android.habitica.helpers
import android.annotation.SuppressLint
import android.content.Context
import android.os.Environment
import com.habitrpg.android.habitica.HabiticaApplication
import java.io.File
import java.io.IOException
import java.util.ArrayList
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okio.BufferedSink
import okio.Okio
import rx.Observable
import rx.exceptions.OnErrorThrowable
import rx.functions.Func1
import rx.schedulers.Schedulers
// based on http://stackoverflow.com/questions/29838565/downloading-files-using-okhttp-okio-and-rxjava
class SoundFileLoader(private val context: Context) {
private val client: OkHttpClient = OkHttpClient()
private val externalCacheDir: String?
get() {
val cacheDir = HabiticaApplication.getInstance(context).getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS)
return cacheDir?.path
}
@SuppressLint("SetWorldReadable", "ObsoleteSdkInt")
fun download(files: List<SoundFile>): Observable<List<SoundFile>> {
return Observable.from(files)
.flatMap({ audioFile ->
val file = File(getFullAudioFilePath(audioFile))
if (file.exists() && file.length() > 5000) {
// Important, or else the MediaPlayer can't access this file
file.setReadable(true, false)
audioFile.file = file
return@flatMap Observable.just(audioFile)
}
val fileObservable = Observable.create<SoundFile> { sub ->
val request = Request.Builder().url(audioFile.webUrl).build()
val response: Response
try {
response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw IOException()
}
} catch (io: IOException) {
throw OnErrorThrowable.from(OnErrorThrowable.addValueAsLastCause(io, audioFile))
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
try {
val sink = Okio.buffer(Okio.sink(file))
sink.writeAll(response.body()!!.source())
sink.flush()
sink.close()
} catch (io: IOException) {
throw OnErrorThrowable.from(OnErrorThrowable.addValueAsLastCause(io, audioFile))
}
file.setReadable(true, false)
audioFile.file = file
sub.onNext(audioFile)
sub.onCompleted()
}
}
fileObservable.subscribeOn(Schedulers.io())
}, 5)
.toList()
.map({ ArrayList(it) })
}
private fun getFullAudioFilePath(soundFile: SoundFile): String =
externalCacheDir + File.separator + soundFile.filePath
}

View file

@ -1,87 +0,0 @@
package com.habitrpg.android.habitica.helpers;
import com.habitrpg.android.habitica.HabiticaBaseApplication;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import rx.Observable;
import rx.schedulers.Schedulers;
public class SoundManager {
public static String SoundAchievementUnlocked = "Achievement_Unlocked";
public static String SoundChat = "Chat";
public static String SoundDaily = "Daily";
public static String SoundDeath = "Death";
public static String SoundItemDrop = "Item_Drop";
public static String SoundLevelUp = "Level_Up";
public static String SoundMinusHabit = "Minus_Habit";
public static String SoundPlusHabit = "Plus_Habit";
public static String SoundReward = "Reward";
public static String SoundTodo = "ToDo";
@Inject
SoundFileLoader soundFileLoader;
private String soundTheme;
private HashMap<String, SoundFile> loadedSoundFiles;
public SoundManager() {
HabiticaBaseApplication.getComponent().inject(this);
loadedSoundFiles = new HashMap<>();
}
public void setSoundTheme(String soundTheme) {
this.soundTheme = soundTheme;
}
public Observable<List<SoundFile>> preloadAllFiles() {
if (soundTheme.equals("off")) {
return Observable.empty();
}
ArrayList<SoundFile> soundFiles = new ArrayList<>();
soundFiles.add(new SoundFile(soundTheme, SoundAchievementUnlocked));
soundFiles.add(new SoundFile(soundTheme, SoundChat));
soundFiles.add(new SoundFile(soundTheme, SoundDaily));
soundFiles.add(new SoundFile(soundTheme, SoundDeath));
soundFiles.add(new SoundFile(soundTheme, SoundItemDrop));
soundFiles.add(new SoundFile(soundTheme, SoundLevelUp));
soundFiles.add(new SoundFile(soundTheme, SoundMinusHabit));
soundFiles.add(new SoundFile(soundTheme, SoundPlusHabit));
soundFiles.add(new SoundFile(soundTheme, SoundReward));
soundFiles.add(new SoundFile(soundTheme, SoundTodo));
return soundFileLoader.download(soundFiles);
}
public void clearLoadedFiles() {
loadedSoundFiles.clear();
}
public void loadAndPlayAudio(String type) {
if ("off".equals(soundTheme) || soundTheme == null) {
return;
}
if (loadedSoundFiles.containsKey(type)) {
loadedSoundFiles.get(type).play();
} else {
ArrayList<SoundFile> soundFiles = new ArrayList<>();
soundFiles.add(new SoundFile(soundTheme, type));
soundFileLoader.download(soundFiles).observeOn(Schedulers.newThread()).subscribe(audioFiles1 -> {
SoundFile file = soundFiles.get(0);
loadedSoundFiles.put(type, file);
file.play();
}, RxErrorHandler.handleEmptyError());
}
}
}

View file

@ -0,0 +1,81 @@
package com.habitrpg.android.habitica.helpers
import android.content.Context
import com.habitrpg.android.habitica.HabiticaBaseApplication
import java.util.ArrayList
import java.util.HashMap
import javax.inject.Inject
import rx.Observable
import rx.functions.Action1
import rx.schedulers.Schedulers
class SoundManager {
@Inject
lateinit var soundFileLoader: SoundFileLoader
@Inject
lateinit var context: Context
var soundTheme: String = SoundThemeOff
private val loadedSoundFiles: MutableMap<String, SoundFile> = HashMap()
init {
HabiticaBaseApplication.getComponent().inject(this)
}
fun preloadAllFiles(): Observable<List<SoundFile>> {
if (soundTheme == SoundThemeOff) {
return Observable.empty()
}
val soundFiles = ArrayList<SoundFile>()
soundFiles.add(SoundFile(soundTheme, SoundAchievementUnlocked))
soundFiles.add(SoundFile(soundTheme, SoundChat))
soundFiles.add(SoundFile(soundTheme, SoundDaily))
soundFiles.add(SoundFile(soundTheme, SoundDeath))
soundFiles.add(SoundFile(soundTheme, SoundItemDrop))
soundFiles.add(SoundFile(soundTheme, SoundLevelUp))
soundFiles.add(SoundFile(soundTheme, SoundMinusHabit))
soundFiles.add(SoundFile(soundTheme, SoundPlusHabit))
soundFiles.add(SoundFile(soundTheme, SoundReward))
soundFiles.add(SoundFile(soundTheme, SoundTodo))
return soundFileLoader.download(soundFiles)
}
fun loadAndPlayAudio(type: String) {
if (soundTheme == SoundThemeOff) {
return
}
if (loadedSoundFiles.containsKey(type)) {
loadedSoundFiles[type]?.play(context)
} else {
val soundFiles = ArrayList<SoundFile>()
soundFiles.add(SoundFile(soundTheme, type))
soundFileLoader.download(soundFiles).observeOn(Schedulers.newThread()).subscribe(Action1 {
val file = soundFiles[0]
loadedSoundFiles.put(type, file)
file.play(context)
}, RxErrorHandler.handleEmptyError())
}
}
companion object {
const val SoundAchievementUnlocked = "Achievement_Unlocked"
const val SoundChat = "Chat"
const val SoundDaily = "Daily"
const val SoundDeath = "Death"
const val SoundItemDrop = "Item_Drop"
const val SoundLevelUp = "Level_Up"
const val SoundMinusHabit = "Minus_Habit"
const val SoundPlusHabit = "Plus_Habit"
const val SoundReward = "Reward"
const val SoundTodo = "ToDo"
const val SoundThemeOff = "off"
}
}

View file

@ -41,7 +41,6 @@ public class LevelUpUseCase extends UseCase<LevelUpUseCase.RequestValues, Stats>
return Observable.defer(() -> {
soundManager.loadAndPlayAudio(SoundManager.SoundLevelUp);
SuppressedModals suppressedModals = requestValues.user.getPreferences().getSuppressModals();
if (suppressedModals != null) {
if (suppressedModals.getLevelUp()) {
@ -54,9 +53,9 @@ public class LevelUpUseCase extends UseCase<LevelUpUseCase.RequestValues, Stats>
View customView = requestValues.activity.getLayoutInflater().inflate(R.layout.dialog_levelup, null);
if (customView != null) {
TextView detailView = (TextView) customView.findViewById(R.id.levelupDetail);
TextView detailView = customView.findViewById(R.id.levelupDetail);
detailView.setText(requestValues.activity.getString(R.string.levelup_detail, requestValues.newLevel));
AvatarView dialogAvatarView = (AvatarView) customView.findViewById(R.id.avatarView);
AvatarView dialogAvatarView = customView.findViewById(R.id.avatarView);
dialogAvatarView.setAvatar(requestValues.user);
}

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.fragments.preferences
import android.annotation.SuppressLint
import android.app.ProgressDialog
import android.content.Intent
import android.content.SharedPreferences
@ -135,6 +136,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
}
@SuppressLint("ObsoleteSdkInt")
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
"use_reminder" -> {
@ -196,7 +198,7 @@ class PreferencesFragment : BasePreferencesFragment(), SharedPreferences.OnShare
val newAudioTheme = sharedPreferences.getString(key, "off")
userRepository.updateUser(user, "preferences.sound", newAudioTheme)
.subscribe(Action1 { }, RxErrorHandler.handleEmptyError())
soundManager.setSoundTheme(newAudioTheme)
soundManager.soundTheme = newAudioTheme
soundManager.preloadAllFiles()
}
"dailyDueDefaultView" -> userRepository.updateUser(user, "preferences.dailyDueDefaultView", sharedPreferences.getBoolean(key, false))