mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-08-01 03:30:34 +00:00
Fix loading sound files. Fixes #819
This commit is contained in:
parent
c45b0d2516
commit
d48a66be00
8 changed files with 223 additions and 263 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in a new issue