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		if (viewHolder.darkBackground) {
 60			viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption_OnDark);
 61		} else {
 62			viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption);
 63		}
 64		viewHolder.progress.setOnSeekBarChangeListener(this);
 65		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 66			ColorStateList color = ContextCompat.getColorStateList(messageAdapter.getContext(), viewHolder.darkBackground ? R.color.white70 : R.color.bubble);
 67			viewHolder.progress.setThumbTintList(color);
 68			viewHolder.progress.setProgressTintList(color);
 69		}
 70		viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
 71		viewHolder.playPause.setOnClickListener(this);
 72		if (message == currentlyPlayingMessage) {
 73			if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) {
 74				viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
 75				viewHolder.progress.setEnabled(true);
 76			} else {
 77				viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
 78				viewHolder.progress.setEnabled(false);
 79			}
 80			return true;
 81		} else {
 82			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
 83			viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
 84			viewHolder.progress.setProgress(0);
 85			viewHolder.progress.setEnabled(false);
 86			return false;
 87		}
 88	}
 89
 90	@Override
 91	public synchronized void onClick(View v) {
 92		if (v.getId() == R.id.play_pause) {
 93			synchronized (LOCK) {
 94				startStop((ImageButton) v);
 95			}
 96		}
 97	}
 98
 99	private void startStop(ImageButton playPause) {
100		final RelativeLayout audioPlayer = (RelativeLayout) playPause.getParent();
101		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
102		final Message message = (Message) audioPlayer.getTag();
103		if (startStop(viewHolder, message)) {
104			this.audioPlayerLayouts.clear();
105			this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
106			stopRefresher(true);
107		}
108	}
109
110	private boolean playPauseCurrent(ViewHolder viewHolder) {
111		viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
112		if (player.isPlaying()) {
113			viewHolder.progress.setEnabled(false);
114			player.pause();
115			messageAdapter.flagScreenOff();
116			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
117		} else {
118			viewHolder.progress.setEnabled(true);
119			player.start();
120			messageAdapter.flagScreenOn();
121			this.stopRefresher(true);
122			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
123		}
124		return false;
125	}
126
127	private boolean play(ViewHolder viewHolder, Message message) {
128		AudioPlayer.player = new MediaPlayer();
129		try {
130			AudioPlayer.currentlyPlayingMessage = message;
131			AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath());
132			AudioPlayer.player.setOnCompletionListener(this);
133			AudioPlayer.player.prepare();
134			AudioPlayer.player.start();
135			messageAdapter.flagScreenOn();
136			viewHolder.progress.setEnabled(true);
137			viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
138			return true;
139		} catch (Exception e) {
140			messageAdapter.flagScreenOff();
141			AudioPlayer.currentlyPlayingMessage = null;
142			return false;
143		}
144	}
145
146	private boolean startStop(ViewHolder viewHolder, Message message) {
147		if (message == currentlyPlayingMessage && player != null) {
148			return playPauseCurrent(viewHolder);
149		}
150		if (AudioPlayer.player != null) {
151			stopCurrent();
152		}
153		return play(viewHolder, message);
154	}
155
156	private void stopCurrent() {
157		if (AudioPlayer.player.isPlaying()) {
158			AudioPlayer.player.stop();
159		}
160		AudioPlayer.player.release();
161		messageAdapter.flagScreenOff();
162		AudioPlayer.player = null;
163		resetPlayerUi();
164	}
165
166	private void resetPlayerUi() {
167		for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
168			resetPlayerUi(audioPlayer.get());
169		}
170	}
171
172	private void resetPlayerUi(RelativeLayout audioPlayer) {
173		if (audioPlayer == null) {
174			return;
175		}
176		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
177		final Message message = (Message) audioPlayer.getTag();
178		viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
179		if (message != null) {
180			viewHolder.runtime.setText(formatTime(message.getFileParams().runtime));
181		}
182		viewHolder.progress.setProgress(0);
183		viewHolder.progress.setEnabled(false);
184	}
185
186	@Override
187	public void onCompletion(MediaPlayer mediaPlayer) {
188		synchronized (AudioPlayer.LOCK) {
189			this.stopRefresher(false);
190			if (AudioPlayer.player == mediaPlayer) {
191				AudioPlayer.currentlyPlayingMessage = null;
192				AudioPlayer.player = null;
193			}
194			mediaPlayer.release();
195			messageAdapter.flagScreenOff();
196			resetPlayerUi();
197		}
198	}
199
200	@Override
201	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
202		synchronized (AudioPlayer.LOCK) {
203			final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent();
204			final Message message = (Message) audioPlayer.getTag();
205			if (fromUser && message == AudioPlayer.currentlyPlayingMessage) {
206				float percent = progress / 100f;
207				int duration = AudioPlayer.player.getDuration();
208				int seekTo = Math.round(duration * percent);
209				AudioPlayer.player.seekTo(seekTo);
210			}
211		}
212	}
213
214	@Override
215	public void onStartTrackingTouch(SeekBar seekBar) {
216
217	}
218
219	@Override
220	public void onStopTrackingTouch(SeekBar seekBar) {
221
222	}
223
224	public void stop() {
225		synchronized (AudioPlayer.LOCK) {
226			stopRefresher(false);
227			if (AudioPlayer.player != null) {
228				stopCurrent();
229			}
230			AudioPlayer.currentlyPlayingMessage = null;
231		}
232	}
233
234	private void stopRefresher(boolean runOnceMore) {
235		this.handler.removeCallbacks(this);
236		if (runOnceMore) {
237			this.handler.post(this);
238		}
239	}
240
241	@Override
242	public void run() {
243		synchronized (AudioPlayer.LOCK) {
244			if (AudioPlayer.player != null) {
245				boolean renew = false;
246				final int current = player.getCurrentPosition();
247				final int duration = player.getDuration();
248				for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
249					renew |= refreshAudioPlayer(audioPlayer.get(), current, duration);
250				}
251				if (renew && AudioPlayer.player.isPlaying()) {
252					handler.postDelayed(this, REFRESH_INTERVAL);
253				}
254			}
255		}
256	}
257
258	private boolean refreshAudioPlayer(RelativeLayout audioPlayer, int current, int duration) {
259		if (audioPlayer == null || audioPlayer.getVisibility() != View.VISIBLE) {
260			return false;
261		}
262		final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
263		viewHolder.progress.setProgress(current * 100 / duration);
264		viewHolder.runtime.setText(formatTime(current) + " / " + formatTime(duration));
265		return true;
266	}
267
268	public static class ViewHolder {
269		private TextView runtime;
270		private SeekBar progress;
271		private ImageButton playPause;
272		private boolean darkBackground = false;
273
274		public static ViewHolder get(RelativeLayout audioPlayer) {
275			ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER);
276			if (viewHolder == null) {
277				viewHolder = new ViewHolder();
278				viewHolder.runtime = (TextView) audioPlayer.findViewById(R.id.runtime);
279				viewHolder.progress = (SeekBar) audioPlayer.findViewById(R.id.progress);
280				viewHolder.playPause = (ImageButton) audioPlayer.findViewById(R.id.play_pause);
281				audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder);
282			}
283			return viewHolder;
284		}
285
286		public void setDarkBackground(boolean darkBackground) {
287			this.darkBackground = darkBackground;
288		}
289	}
290}