play audio files inline

This commit is contained in:
Daniel Gultsch 2017-09-20 15:35:35 +02:00
parent 30b6201b95
commit 20b14091d6
26 changed files with 395 additions and 22 deletions

View file

@ -868,6 +868,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override
public void onStop() {
super.onStop();
if (activity == null || !activity.isChangingConfigurations()) {
messageListAdapter.stopAudioPlayer();
}
if (this.conversation != null) {
final String msg = mEditMessage.getText().toString();
this.conversation.setNextMessage(msg);
@ -894,6 +897,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.conversation.setNextMessage(msg);
if (this.conversation != conversation) {
updateChatState(this.conversation, msg);
messageListAdapter.stopAudioPlayer();
}
this.conversation.trim();

View file

@ -33,6 +33,7 @@ import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
@ -59,6 +60,7 @@ import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.NotificationService;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.service.AudioPlayer;
import eu.siacs.conversations.ui.text.DividerSpan;
import eu.siacs.conversations.ui.text.QuoteSpan;
import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
@ -120,8 +122,11 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
private final ListSelectionManager listSelectionManager = new ListSelectionManager();
private final AudioPlayer audioPlayer;
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
super(activity, 0, messages);
this.audioPlayer = new AudioPlayer(this);
this.activity = activity;
metrics = getContext().getResources().getDisplayMetrics();
updatePreferences();
@ -164,7 +169,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
return this.getItemViewType(getItem(position));
}
private int getMessageTextColor(boolean onDark, boolean primary) {
public int getMessageTextColor(boolean onDark, boolean primary) {
if (onDark) {
return ContextCompat.getColor(activity, primary ? R.color.white : R.color.white70);
} else {
@ -299,9 +304,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(text);
@ -311,10 +315,9 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setText(getContext().getString(
R.string.decryption_failed));
@ -324,9 +327,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
private void displayEmojiMessage(final ViewHolder viewHolder, final String body) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setIncludeFontPadding(false);
@ -406,10 +408,9 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground, int type) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.VISIBLE);
viewHolder.messageBody.setIncludeFontPadding(true);
if (message.getBody() != null) {
@ -492,10 +493,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
}
private void displayDownloadableMessage(ViewHolder viewHolder,
final Message message, String text) {
private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text) {
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.VISIBLE);
viewHolder.download_button.setText(text);
viewHolder.download_button.setOnClickListener(new OnClickListener() {
@ -510,6 +511,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.VISIBLE);
viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
viewHolder.download_button.setOnClickListener(new OnClickListener() {
@ -524,6 +526,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.VISIBLE);
viewHolder.download_button.setText(R.string.show_location);
viewHolder.download_button.setOnClickListener(new OnClickListener() {
@ -535,12 +538,21 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
});
}
private void displayImageMessage(ViewHolder viewHolder,
final Message message) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
}
private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) {
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.download_button.setVisibility(View.GONE);
final RelativeLayout audioPlayer = viewHolder.audioPlayer;
audioPlayer.setVisibility(View.VISIBLE);
AudioPlayer.ViewHolder.get(audioPlayer).setDarkBackground(darkBackground);
this.audioPlayer.init(audioPlayer, message);
}
private void displayImageMessage(ViewHolder viewHolder, final Message message) {
viewHolder.download_button.setVisibility(View.GONE);
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.audioPlayer.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.VISIBLE);
FileParams params = message.getFileParams();
double target = metrics.density * 288;
@ -627,6 +639,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
.findViewById(R.id.message_time);
viewHolder.indicatorReceived = (ImageView) view
.findViewById(R.id.indicator_received);
viewHolder.audioPlayer = (RelativeLayout) view.findViewById(R.id.audio_player);
break;
case RECEIVED:
view = activity.getLayoutInflater().inflate(
@ -649,6 +662,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
viewHolder.indicatorReceived = (ImageView) view
.findViewById(R.id.indicator_received);
viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption);
viewHolder.audioPlayer = (RelativeLayout) view.findViewById(R.id.audio_player);
break;
case STATUS:
view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
@ -762,8 +776,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
displayImageMessage(viewHolder, message);
} else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
if (message.getFileParams().width > 0) {
displayImageMessage(viewHolder,message);
if (message.getFileParams().width > 0 && message.getFileParams().height > 0) {
displayImageMessage(viewHolder, message);
} else if (message.getFileParams().runtime > 0) {
displayAudioMessage(viewHolder, message, darkBackground);
} else {
displayOpenableMessage(viewHolder, message);
}
@ -877,6 +893,14 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
}
public FileBackend getFileBackend() {
return activity.xmppConnectionService.getFileBackend();
}
public void stopAudioPlayer() {
audioPlayer.stop();
}
public interface OnQuoteListener {
public void onQuote(String text);
}
@ -1004,6 +1028,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
protected TextView encryption;
public Button load_more_messages;
public ImageView edit_indicator;
public RelativeLayout audioPlayer;
}
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {

View file

@ -0,0 +1,280 @@
package eu.siacs.conversations.ui.service;
import android.content.res.ColorStateList;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Handler;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.Locale;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.ui.adapter.MessageAdapter;
import eu.siacs.conversations.utils.WeakReferenceSet;
public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable {
private static final int REFRESH_INTERVAL = 250;
private static final Object LOCK = new Object();
private static MediaPlayer player = null;
private static Message currentlyPlayingMessage = null;
private final MessageAdapter messageAdapter;
private final WeakReferenceSet<RelativeLayout> audioPlayerLayouts = new WeakReferenceSet<>();
private final Handler handler = new Handler();
public AudioPlayer(MessageAdapter adapter) {
this.messageAdapter = adapter;
synchronized (AudioPlayer.LOCK) {
if (AudioPlayer.player != null) {
AudioPlayer.player.setOnCompletionListener(this);
}
}
}
private static String formatTime(int ms) {
return String.format(Locale.ENGLISH, "%d:%02d", ms / 60000, Math.min(Math.round((ms % 60000) / 1000f), 59));
}
public void init(RelativeLayout audioPlayer, Message message) {
synchronized (AudioPlayer.LOCK) {
audioPlayer.setTag(message);
if (init(ViewHolder.get(audioPlayer), message)) {
this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
this.stopRefresher(true);
} else {
this.audioPlayerLayouts.removeWeakReferenceTo(audioPlayer);
}
}
}
private boolean init(ViewHolder viewHolder, Message message) {
viewHolder.runtime.setTextColor(this.messageAdapter.getMessageTextColor(viewHolder.darkBackground, false));
viewHolder.progress.setOnSeekBarChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ColorStateList color = ContextCompat.getColorStateList(messageAdapter.getContext(), viewHolder.darkBackground ? R.color.white70 : R.color.bubble);
viewHolder.progress.setThumbTintList(color);
viewHolder.progress.setProgressTintList(color);
}
viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
viewHolder.playPause.setOnClickListener(this);
if (message == currentlyPlayingMessage) {
if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) {
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
viewHolder.progress.setEnabled(true);
} else {
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
viewHolder.progress.setEnabled(false);
}
return true;
} else {
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
viewHolder.progress.setProgress(0);
viewHolder.progress.setEnabled(false);
return false;
}
}
@Override
public synchronized void onClick(View v) {
if (v.getId() == R.id.play_pause) {
synchronized (LOCK) {
startStop((ImageButton) v);
}
}
}
private void startStop(ImageButton playPause) {
final RelativeLayout audioPlayer = (RelativeLayout) playPause.getParent();
final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
final Message message = (Message) audioPlayer.getTag();
if (startStop(viewHolder, message)) {
this.audioPlayerLayouts.clear();
this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
stopRefresher(true);
}
}
private boolean playPauseCurrent(ViewHolder viewHolder) {
viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
if (player.isPlaying()) {
viewHolder.progress.setEnabled(false);
player.pause();
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
} else {
viewHolder.progress.setEnabled(true);
player.start();
this.stopRefresher(true);
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
}
return false;
}
private boolean play(ViewHolder viewHolder, Message message) {
AudioPlayer.player = new MediaPlayer();
try {
AudioPlayer.currentlyPlayingMessage = message;
AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath());
AudioPlayer.player.setOnCompletionListener(this);
AudioPlayer.player.prepare();
AudioPlayer.player.start();
viewHolder.progress.setEnabled(true);
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
return true;
} catch (Exception e) {
AudioPlayer.currentlyPlayingMessage = null;
return false;
}
}
private boolean startStop(ViewHolder viewHolder, Message message) {
if (message == currentlyPlayingMessage && player != null) {
return playPauseCurrent(viewHolder);
}
if (AudioPlayer.player != null) {
stopCurrent();
}
return play(viewHolder, message);
}
private void stopCurrent() {
if (AudioPlayer.player.isPlaying()) {
AudioPlayer.player.stop();
}
AudioPlayer.player.release();
AudioPlayer.player = null;
resetPlayerUi();
}
private void resetPlayerUi() {
for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
resetPlayerUi(audioPlayer.get());
}
}
private void resetPlayerUi(RelativeLayout audioPlayer) {
if (audioPlayer == null) {
return;
}
final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
final Message message = (Message) audioPlayer.getTag();
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
if (message != null) {
viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
}
viewHolder.progress.setProgress(0);
viewHolder.progress.setEnabled(false);
}
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
synchronized (AudioPlayer.LOCK) {
this.stopRefresher(false);
if (AudioPlayer.player == mediaPlayer) {
AudioPlayer.currentlyPlayingMessage = null;
AudioPlayer.player = null;
}
mediaPlayer.release();
resetPlayerUi();
}
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
synchronized (AudioPlayer.LOCK) {
final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent();
final Message message = (Message) audioPlayer.getTag();
if (fromUser && message == AudioPlayer.currentlyPlayingMessage) {
float percent = progress / 100f;
int duration = AudioPlayer.player.getDuration();
int seekTo = Math.round(duration * percent);
AudioPlayer.player.seekTo(seekTo);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
public void stop() {
synchronized (AudioPlayer.LOCK) {
stopRefresher(false);
if (AudioPlayer.player != null) {
stopCurrent();
}
AudioPlayer.currentlyPlayingMessage = null;
}
}
private void stopRefresher(boolean runOnceMore) {
this.handler.removeCallbacks(this);
if (runOnceMore) {
this.handler.post(this);
}
}
@Override
public void run() {
synchronized (AudioPlayer.LOCK) {
if (AudioPlayer.player != null) {
boolean renew = false;
final int current = player.getCurrentPosition();
final int duration = player.getDuration();
for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
renew |= refreshAudioPlayer(audioPlayer.get(), current, duration);
}
if (renew && AudioPlayer.player.isPlaying()) {
handler.postDelayed(this, REFRESH_INTERVAL);
}
}
}
}
private boolean refreshAudioPlayer(RelativeLayout audioPlayer, int current, int duration) {
if (audioPlayer == null || audioPlayer.getVisibility() != View.VISIBLE) {
return false;
}
final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
viewHolder.progress.setProgress(current * 100 / duration);
viewHolder.runtime.setText(formatTime(current) + " / " + formatTime(duration));
return true;
}
public static class ViewHolder {
private TextView runtime;
private SeekBar progress;
private ImageButton playPause;
private boolean darkBackground = false;
public static ViewHolder get(RelativeLayout audioPlayer) {
ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER);
if (viewHolder == null) {
viewHolder = new ViewHolder();
viewHolder.runtime = (TextView) audioPlayer.findViewById(R.id.runtime);
viewHolder.progress = (SeekBar) audioPlayer.findViewById(R.id.progress);
viewHolder.playPause = (ImageButton) audioPlayer.findViewById(R.id.play_pause);
audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder);
}
return viewHolder;
}
public void setDarkBackground(boolean darkBackground) {
this.darkBackground = darkBackground;
}
}
}

View file

@ -0,0 +1,26 @@
package eu.siacs.conversations.utils;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Iterator;
public class WeakReferenceSet<T> extends HashSet<WeakReference<T>> {
public void removeWeakReferenceTo(T reference) {
for (Iterator<WeakReference<T>> iterator = iterator(); iterator.hasNext(); ) {
if (reference == iterator.next().get()) {
iterator.remove();
}
}
}
public void addWeakReferenceTo(T reference) {
for (WeakReference<T> weakReference : this) {
if (reference == weakReference.get()) {
return;
}
}
this.add(new WeakReference<>(reference));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

View file

@ -30,4 +30,41 @@
android:layout_height="wrap_content"
android:longClickable="true"
android:visibility="gone"/>
<RelativeLayout
android:id="@+id/audio_player"
android:layout_width="288dp"
android:layout_height="wrap_content"
android:visibility="gone"
>
<ImageButton
android:id="@+id/play_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:alpha="?attr/icon_alpha"
android:background="?android:selectableItemBackground"/>
<TextView
android:id="@+id/runtime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:paddingBottom="16dp"
android:paddingRight="16dp"
android:textColor="?attr/color_text_secondary"
android:textSize="?attr/TextSizeInfo"/>
<SeekBar
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/runtime"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/play_pause"
android:progress="100"/>
</RelativeLayout>
</merge>

View file

@ -3,4 +3,5 @@
<item type="id" name="TAG_ACCOUNT"/>
<item type="id" name="TAG_FINGERPRINT"/>
<item type="id" name="TAG_FINGERPRINT_STATUS"/>
<item type="id" name="TAG_AUDIO_PLAYER_VIEW_HOLDER"/>
</resources>