AudioPlayer.java

  1package eu.siacs.conversations.ui.service;
  2
  3import android.content.res.ColorStateList;
  4import android.media.MediaPlayer;
  5import android.os.Build;
  6import android.os.Handler;
  7import android.support.v4.content.ContextCompat;
  8import android.view.View;
  9import android.widget.ImageButton;
 10import android.widget.RelativeLayout;
 11import android.widget.SeekBar;
 12import android.widget.TextView;
 13
 14import java.lang.ref.WeakReference;
 15import java.util.Locale;
 16
 17import eu.siacs.conversations.R;
 18import eu.siacs.conversations.entities.Message;
 19import eu.siacs.conversations.ui.adapter.MessageAdapter;
 20import eu.siacs.conversations.utils.WeakReferenceSet;
 21
 22public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable {
 23
 24	private static final int REFRESH_INTERVAL = 250;
 25	private static final Object LOCK = new Object();
 26	private static MediaPlayer player = null;
 27	private static Message currentlyPlayingMessage = null;
 28	private final MessageAdapter messageAdapter;
 29	private final WeakReferenceSet<RelativeLayout> audioPlayerLayouts = new WeakReferenceSet<>();
 30
 31	private final Handler handler = new Handler();
 32
 33	public AudioPlayer(MessageAdapter adapter) {
 34		this.messageAdapter = adapter;
 35		synchronized (AudioPlayer.LOCK) {
 36			if (AudioPlayer.player != null) {
 37				AudioPlayer.player.setOnCompletionListener(this);
 38			}
 39		}
 40	}
 41
 42	private static String formatTime(int ms) {
 43		return String.format(Locale.ENGLISH, "%d:%02d", ms / 60000, Math.min(Math.round((ms % 60000) / 1000f), 59));
 44	}
 45
 46	public void init(RelativeLayout audioPlayer, Message message) {
 47		synchronized (AudioPlayer.LOCK) {
 48			audioPlayer.setTag(message);
 49			if (init(ViewHolder.get(audioPlayer), message)) {
 50				this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
 51				this.stopRefresher(true);
 52			} else {
 53				this.audioPlayerLayouts.removeWeakReferenceTo(audioPlayer);
 54			}
 55		}
 56	}
 57
 58	private boolean init(ViewHolder viewHolder, Message message) {
 59		viewHolder.runtime.setTextColor(this.messageAdapter.getMessageTextColor(viewHolder.darkBackground, false));
 60		viewHolder.progress.setOnSeekBarChangeListener(this);
 61		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 62			ColorStateList color = ContextCompat.getColorStateList(messageAdapter.getContext(), viewHolder.darkBackground ? R.color.white70 : R.color.bubble);
 63			viewHolder.progress.setThumbTintList(color);
 64			viewHolder.progress.setProgressTintList(color);
 65		}
 66		viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
 67		viewHolder.playPause.setOnClickListener(this);
 68		if (message == currentlyPlayingMessage) {
 69			if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) {
 70				viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
 71				viewHolder.progress.setEnabled(true);
 72			} else {
 73				viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
 74				viewHolder.progress.setEnabled(false);
 75			}
 76			return true;
 77		} else {
 78			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
 79			viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
 80			viewHolder.progress.setProgress(0);
 81			viewHolder.progress.setEnabled(false);
 82			return false;
 83		}
 84	}
 85
 86	@Override
 87	public synchronized void onClick(View v) {
 88		if (v.getId() == R.id.play_pause) {
 89			synchronized (LOCK) {
 90				startStop((ImageButton) v);
 91			}
 92		}
 93	}
 94
 95	private void startStop(ImageButton playPause) {
 96		final RelativeLayout audioPlayer = (RelativeLayout) playPause.getParent();
 97		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
 98		final Message message = (Message) audioPlayer.getTag();
 99		if (startStop(viewHolder, message)) {
100			this.audioPlayerLayouts.clear();
101			this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
102			stopRefresher(true);
103		}
104	}
105
106	private boolean playPauseCurrent(ViewHolder viewHolder) {
107		viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
108		if (player.isPlaying()) {
109			viewHolder.progress.setEnabled(false);
110			player.pause();
111			messageAdapter.flagScreenOff();
112			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
113		} else {
114			viewHolder.progress.setEnabled(true);
115			player.start();
116			messageAdapter.flagScreenOn();
117			this.stopRefresher(true);
118			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
119		}
120		return false;
121	}
122
123	private boolean play(ViewHolder viewHolder, Message message) {
124		AudioPlayer.player = new MediaPlayer();
125		try {
126			AudioPlayer.currentlyPlayingMessage = message;
127			AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath());
128			AudioPlayer.player.setOnCompletionListener(this);
129			AudioPlayer.player.prepare();
130			AudioPlayer.player.start();
131			messageAdapter.flagScreenOn();
132			viewHolder.progress.setEnabled(true);
133			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
134			return true;
135		} catch (Exception e) {
136			messageAdapter.flagScreenOff();
137			AudioPlayer.currentlyPlayingMessage = null;
138			return false;
139		}
140	}
141
142	private boolean startStop(ViewHolder viewHolder, Message message) {
143		if (message == currentlyPlayingMessage && player != null) {
144			return playPauseCurrent(viewHolder);
145		}
146		if (AudioPlayer.player != null) {
147			stopCurrent();
148		}
149		return play(viewHolder, message);
150	}
151
152	private void stopCurrent() {
153		if (AudioPlayer.player.isPlaying()) {
154			AudioPlayer.player.stop();
155		}
156		AudioPlayer.player.release();
157		messageAdapter.flagScreenOff();
158		AudioPlayer.player = null;
159		resetPlayerUi();
160	}
161
162	private void resetPlayerUi() {
163		for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
164			resetPlayerUi(audioPlayer.get());
165		}
166	}
167
168	private void resetPlayerUi(RelativeLayout audioPlayer) {
169		if (audioPlayer == null) {
170			return;
171		}
172		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
173		final Message message = (Message) audioPlayer.getTag();
174		viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
175		if (message != null) {
176			viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
177		}
178		viewHolder.progress.setProgress(0);
179		viewHolder.progress.setEnabled(false);
180	}
181
182	@Override
183	public void onCompletion(MediaPlayer mediaPlayer) {
184		synchronized (AudioPlayer.LOCK) {
185			this.stopRefresher(false);
186			if (AudioPlayer.player == mediaPlayer) {
187				AudioPlayer.currentlyPlayingMessage = null;
188				AudioPlayer.player = null;
189			}
190			mediaPlayer.release();
191			messageAdapter.flagScreenOff();
192			resetPlayerUi();
193		}
194	}
195
196	@Override
197	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
198		synchronized (AudioPlayer.LOCK) {
199			final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent();
200			final Message message = (Message) audioPlayer.getTag();
201			if (fromUser && message == AudioPlayer.currentlyPlayingMessage) {
202				float percent = progress / 100f;
203				int duration = AudioPlayer.player.getDuration();
204				int seekTo = Math.round(duration * percent);
205				AudioPlayer.player.seekTo(seekTo);
206			}
207		}
208	}
209
210	@Override
211	public void onStartTrackingTouch(SeekBar seekBar) {
212
213	}
214
215	@Override
216	public void onStopTrackingTouch(SeekBar seekBar) {
217
218	}
219
220	public void stop() {
221		synchronized (AudioPlayer.LOCK) {
222			stopRefresher(false);
223			if (AudioPlayer.player != null) {
224				stopCurrent();
225			}
226			AudioPlayer.currentlyPlayingMessage = null;
227		}
228	}
229
230	private void stopRefresher(boolean runOnceMore) {
231		this.handler.removeCallbacks(this);
232		if (runOnceMore) {
233			this.handler.post(this);
234		}
235	}
236
237	@Override
238	public void run() {
239		synchronized (AudioPlayer.LOCK) {
240			if (AudioPlayer.player != null) {
241				boolean renew = false;
242				final int current = player.getCurrentPosition();
243				final int duration = player.getDuration();
244				for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
245					renew |= refreshAudioPlayer(audioPlayer.get(), current, duration);
246				}
247				if (renew && AudioPlayer.player.isPlaying()) {
248					handler.postDelayed(this, REFRESH_INTERVAL);
249				}
250			}
251		}
252	}
253
254	private boolean refreshAudioPlayer(RelativeLayout audioPlayer, int current, int duration) {
255		if (audioPlayer == null || audioPlayer.getVisibility() != View.VISIBLE) {
256			return false;
257		}
258		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
259		viewHolder.progress.setProgress(current * 100 / duration);
260		viewHolder.runtime.setText(formatTime(current) + " / " + formatTime(duration));
261		return true;
262	}
263
264	public static class ViewHolder {
265		private TextView runtime;
266		private SeekBar progress;
267		private ImageButton playPause;
268		private boolean darkBackground = false;
269
270		public static ViewHolder get(RelativeLayout audioPlayer) {
271			ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER);
272			if (viewHolder == null) {
273				viewHolder = new ViewHolder();
274				viewHolder.runtime = (TextView) audioPlayer.findViewById(R.id.runtime);
275				viewHolder.progress = (SeekBar) audioPlayer.findViewById(R.id.progress);
276				viewHolder.playPause = (ImageButton) audioPlayer.findViewById(R.id.play_pause);
277				audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder);
278			}
279			return viewHolder;
280		}
281
282		public void setDarkBackground(boolean darkBackground) {
283			this.darkBackground = darkBackground;
284		}
285	}
286}