mirror of
https://github.com/nextcloud/android.git
synced 2024-11-26 15:15:51 +03:00
Merge pull request #12952 from nextcloud/bugfix/media-control-view-visibility
Fix Media Control View Visibility
This commit is contained in:
commit
361f9ddbb2
10 changed files with 429 additions and 390 deletions
|
@ -1,343 +0,0 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2018-2020 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2018 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
||||
* SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
|
||||
* SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
||||
*/
|
||||
package com.owncloud.android.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.MediaController.MediaPlayerControl;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
|
||||
import com.owncloud.android.MainApp;
|
||||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.databinding.MediaControlBinding;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
||||
|
||||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* View containing controls for a {@link MediaPlayer}.
|
||||
* <p>
|
||||
* Holds buttons "play / pause", "rewind", "fast forward" and a progress slider.
|
||||
* <p>
|
||||
* It synchronizes itself with the state of the {@link MediaPlayer}.
|
||||
*/
|
||||
public class MediaControlView extends LinearLayout implements OnClickListener, OnSeekBarChangeListener {
|
||||
private static final String TAG = MediaControlView.class.getSimpleName();
|
||||
private static final int SHOW_PROGRESS = 1;
|
||||
|
||||
private MediaPlayerControl playerControl;
|
||||
private final MediaControlBinding binding;
|
||||
private boolean isDragging;
|
||||
|
||||
@Inject
|
||||
ViewThemeUtils viewThemeUtils;
|
||||
|
||||
public MediaControlView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
MainApp.getAppComponent().inject(this);
|
||||
|
||||
LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
binding = MediaControlBinding.inflate(inflate, this, true);
|
||||
initControllerView();
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
public void setMediaPlayer(MediaPlayerControl player) {
|
||||
playerControl = player;
|
||||
handler.sendEmptyMessage(SHOW_PROGRESS);
|
||||
handler.postDelayed(() -> {
|
||||
updatePausePlay();
|
||||
setProgress();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
public void stopMediaPlayerMessages() {
|
||||
handler.removeMessages(SHOW_PROGRESS);
|
||||
}
|
||||
|
||||
private void initControllerView() {
|
||||
binding.playBtn.requestFocus();
|
||||
binding.playBtn.setOnClickListener(this);
|
||||
|
||||
binding.forwardBtn.setOnClickListener(this);
|
||||
|
||||
binding.rewindBtn.setOnClickListener(this);
|
||||
|
||||
viewThemeUtils.platform.themeHorizontalSeekBar(binding.progressBar);
|
||||
binding.progressBar.setOnSeekBarChangeListener(this);
|
||||
binding.progressBar.setMax(1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable pause or seek buttons if the stream cannot be paused or seeked.
|
||||
* This requires the control interface to be a MediaPlayerControlExt
|
||||
*/
|
||||
private void disableUnsupportedButtons() {
|
||||
try {
|
||||
if (binding != null) {
|
||||
if (!playerControl.canPause()) {
|
||||
binding.playBtn.setEnabled(false);
|
||||
}
|
||||
if (!playerControl.canSeekBackward()) {
|
||||
binding.rewindBtn.setEnabled(false);
|
||||
}
|
||||
if (!playerControl.canSeekForward()) {
|
||||
binding.forwardBtn.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IncompatibleClassChangeError ex) {
|
||||
// We were given an old version of the interface, that doesn't have
|
||||
// the canPause/canSeekXYZ methods. This is OK, it just means we
|
||||
// assume the media can be paused and seeked, and so we don't disable
|
||||
// the buttons.
|
||||
Log_OC.i(TAG, "Old media interface detected");
|
||||
}
|
||||
}
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.what == SHOW_PROGRESS) {
|
||||
updatePausePlay();
|
||||
int pos = setProgress();
|
||||
if (!isDragging) {
|
||||
sendMessageDelayed(obtainMessage(SHOW_PROGRESS), 1000 - (pos % 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private String formatTime(int timeMs) {
|
||||
int totalSeconds = timeMs / 1000;
|
||||
|
||||
int seconds = totalSeconds % 60;
|
||||
int minutes = (totalSeconds / 60) % 60;
|
||||
int hours = totalSeconds / 3600;
|
||||
|
||||
final StringBuilder mFormatBuilder = new StringBuilder();
|
||||
final Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
|
||||
if (hours > 0) {
|
||||
return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
|
||||
} else {
|
||||
return mFormatter.format("%02d:%02d", minutes, seconds).toString();
|
||||
}
|
||||
}
|
||||
|
||||
private int setProgress() {
|
||||
if (playerControl == null || isDragging) {
|
||||
return 0;
|
||||
}
|
||||
int position = playerControl.getCurrentPosition();
|
||||
int duration = playerControl.getDuration();
|
||||
if (binding != null) {
|
||||
if (duration > 0) {
|
||||
// use long to avoid overflow
|
||||
long pos = 1000L * position / duration;
|
||||
binding.progressBar.setProgress((int) pos);
|
||||
}
|
||||
int percent = playerControl.getBufferPercentage();
|
||||
binding.progressBar.setSecondaryProgress(percent * 10);
|
||||
|
||||
String endTime = duration > 0 ? formatTime(duration) : "--:--";
|
||||
binding.totalTimeText.setText(endTime);
|
||||
binding.currentTimeText.setText(formatTime(position));
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
int keyCode = event.getKeyCode();
|
||||
final boolean uniqueDown = event.getRepeatCount() == 0
|
||||
&& event.getAction() == KeyEvent.ACTION_DOWN;
|
||||
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK
|
||||
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|
||||
|| keyCode == KeyEvent.KEYCODE_SPACE) {
|
||||
if (uniqueDown) {
|
||||
doPauseResume();
|
||||
//show(sDefaultTimeout);
|
||||
if (binding != null) {
|
||||
binding.playBtn.requestFocus();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
|
||||
if (uniqueDown && !playerControl.isPlaying()) {
|
||||
playerControl.start();
|
||||
updatePausePlay();
|
||||
}
|
||||
return true;
|
||||
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
|
||||
|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
|
||||
if (uniqueDown && playerControl.isPlaying()) {
|
||||
playerControl.pause();
|
||||
updatePausePlay();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
public void updatePausePlay() {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerControl.isPlaying()) {
|
||||
binding.playBtn.setImageResource(android.R.drawable.ic_media_pause);
|
||||
} else {
|
||||
binding.playBtn.setImageResource(android.R.drawable.ic_media_play);
|
||||
}
|
||||
|
||||
final boolean canSeekFfd = playerControl.canSeekForward();
|
||||
if (canSeekFfd) {
|
||||
binding.forwardBtn.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.forwardBtn.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
final boolean canSeekBwd = playerControl.canSeekBackward();
|
||||
if (canSeekBwd) {
|
||||
binding.rewindBtn.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.rewindBtn.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void doPauseResume() {
|
||||
if (playerControl.isPlaying()) {
|
||||
playerControl.pause();
|
||||
} else {
|
||||
playerControl.start();
|
||||
}
|
||||
updatePausePlay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
if(binding!=null){
|
||||
binding.playBtn.setEnabled(enabled);
|
||||
binding.forwardBtn.setEnabled(enabled);
|
||||
binding.rewindBtn.setEnabled(enabled);
|
||||
binding.progressBar.setEnabled(enabled);
|
||||
}
|
||||
|
||||
disableUnsupportedButtons();
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int pos;
|
||||
boolean playing = playerControl.isPlaying();
|
||||
int id = v.getId();
|
||||
|
||||
if (id == R.id.playBtn) {
|
||||
doPauseResume();
|
||||
} else if (id == R.id.rewindBtn) {
|
||||
pos = playerControl.getCurrentPosition();
|
||||
pos -= 5000;
|
||||
playerControl.seekTo(pos);
|
||||
if (!playing) {
|
||||
playerControl.pause(); // necessary in some 2.3.x devices
|
||||
}
|
||||
setProgress();
|
||||
} else if (id == R.id.forwardBtn) {
|
||||
pos = playerControl.getCurrentPosition();
|
||||
pos += 15000;
|
||||
playerControl.seekTo(pos);
|
||||
if (!playing) {
|
||||
playerControl.pause(); // necessary in some 2.3.x devices
|
||||
}
|
||||
setProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (!fromUser) {
|
||||
// We're not interested in programmatically generated changes to
|
||||
// the progress bar's position.
|
||||
return;
|
||||
}
|
||||
|
||||
long duration = playerControl.getDuration();
|
||||
long newPosition = (duration * progress) / 1000L;
|
||||
playerControl.seekTo((int) newPosition);
|
||||
binding.currentTimeText.setText(formatTime((int) newPosition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in devices with touchpad when the user starts to adjust the position of the seekbar's thumb.
|
||||
*
|
||||
* Will be followed by several onProgressChanged notifications.
|
||||
*/
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
isDragging = true; // monitors the duration of dragging
|
||||
handler.removeMessages(SHOW_PROGRESS); // grants no more updates with media player progress while dragging
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in devices with touchpad when the user finishes the adjusting of the seekbar.
|
||||
*/
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
isDragging = false;
|
||||
setProgress();
|
||||
updatePausePlay();
|
||||
handler.sendEmptyMessage(SHOW_PROGRESS); // grants future updates with media player progress
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
|
||||
super.onInitializeAccessibilityEvent(event);
|
||||
event.setClassName(MediaControlView.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||
super.onInitializeAccessibilityNodeInfo(info);
|
||||
info.setClassName(MediaControlView.class.getName());
|
||||
}
|
||||
}
|
347
app/src/main/java/com/owncloud/android/media/MediaControlView.kt
Normal file
347
app/src/main/java/com/owncloud/android/media/MediaControlView.kt
Normal file
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey Vilas <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2018-2020 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2018 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
|
||||
* SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
|
||||
* SPDX-License-Identifier: GPL-2.0-only AND AGPL-3.0-or-later
|
||||
*/
|
||||
package com.owncloud.android.media
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.MediaController.MediaPlayerControl
|
||||
import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.owncloud.android.MainApp
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.databinding.MediaControlBinding
|
||||
import com.owncloud.android.lib.common.utils.Log_OC
|
||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||
import java.util.Formatter
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* View containing controls for a MediaPlayer.
|
||||
*
|
||||
*
|
||||
* Holds buttons "play / pause", "rewind", "fast forward" and a progress slider.
|
||||
*
|
||||
*
|
||||
* It synchronizes itself with the state of the MediaPlayer.
|
||||
*/
|
||||
class MediaControlView(context: Context, attrs: AttributeSet?) :
|
||||
LinearLayout(context, attrs),
|
||||
View.OnClickListener,
|
||||
OnSeekBarChangeListener {
|
||||
|
||||
private var playerControl: MediaPlayerControl? = null
|
||||
private var binding: MediaControlBinding
|
||||
private var isDragging = false
|
||||
|
||||
@Inject
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
|
||||
public override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun setMediaPlayer(player: MediaPlayerControl?) {
|
||||
playerControl = player
|
||||
handler.sendEmptyMessage(SHOW_PROGRESS)
|
||||
|
||||
handler.postDelayed({
|
||||
updatePausePlay()
|
||||
setProgress()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
fun stopMediaPlayerMessages() {
|
||||
handler.removeMessages(SHOW_PROGRESS)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun initControllerView() {
|
||||
binding.playBtn.requestFocus()
|
||||
|
||||
binding.playBtn.setOnClickListener(this)
|
||||
binding.forwardBtn.setOnClickListener(this)
|
||||
binding.rewindBtn.setOnClickListener(this)
|
||||
|
||||
binding.progressBar.run {
|
||||
viewThemeUtils.platform.themeHorizontalSeekBar(this)
|
||||
setMax(1000)
|
||||
}
|
||||
|
||||
binding.progressBar.setOnSeekBarChangeListener(this)
|
||||
|
||||
viewThemeUtils.material.run {
|
||||
colorMaterialButtonPrimaryTonal(binding.rewindBtn)
|
||||
colorMaterialButtonPrimaryTonal(binding.playBtn)
|
||||
colorMaterialButtonPrimaryTonal(binding.forwardBtn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable pause or seek buttons if the stream cannot be paused or seeked.
|
||||
* This requires the control interface to be a MediaPlayerControlExt
|
||||
*/
|
||||
private fun disableUnsupportedButtons() {
|
||||
try {
|
||||
if (playerControl?.canPause() == false) {
|
||||
binding.playBtn.setEnabled(false)
|
||||
}
|
||||
if (playerControl?.canSeekBackward() == false) {
|
||||
binding.rewindBtn.setEnabled(false)
|
||||
}
|
||||
if (playerControl?.canSeekForward() == false) {
|
||||
binding.forwardBtn.setEnabled(false)
|
||||
}
|
||||
} catch (ex: IncompatibleClassChangeError) {
|
||||
// We were given an old version of the interface, that doesn't have
|
||||
// the canPause/canSeekXYZ methods. This is OK, it just means we
|
||||
// assume the media can be paused and seeked, and so we don't disable
|
||||
// the buttons.
|
||||
Log_OC.i(TAG, "Old media interface detected")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private val handler: Handler = object : Handler(Looper.getMainLooper()) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
if (msg.what == SHOW_PROGRESS) {
|
||||
updatePausePlay()
|
||||
val pos = setProgress()
|
||||
|
||||
if (!isDragging) {
|
||||
sendMessageDelayed(obtainMessage(SHOW_PROGRESS), (1000 - pos % 1000).toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
MainApp.getAppComponent().inject(this)
|
||||
|
||||
val inflate = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
binding = MediaControlBinding.inflate(inflate, this, true)
|
||||
initControllerView()
|
||||
isFocusable = true
|
||||
setFocusableInTouchMode(true)
|
||||
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS)
|
||||
requestFocus()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun formatTime(timeMs: Int): String {
|
||||
val totalSeconds = timeMs / 1000
|
||||
val seconds = totalSeconds % 60
|
||||
val minutes = totalSeconds / 60 % 60
|
||||
val hours = totalSeconds / 3600
|
||||
val mFormatBuilder = StringBuilder()
|
||||
val mFormatter = Formatter(mFormatBuilder, Locale.getDefault())
|
||||
return if (hours > 0) {
|
||||
mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
|
||||
} else {
|
||||
mFormatter.format("%02d:%02d", minutes, seconds).toString()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setProgress(): Int {
|
||||
var position = 0
|
||||
if (playerControl == null || isDragging) {
|
||||
position = 0
|
||||
}
|
||||
|
||||
playerControl?.let { playerControl ->
|
||||
position = playerControl.currentPosition
|
||||
val duration = playerControl.duration
|
||||
if (duration > 0) {
|
||||
// use long to avoid overflow
|
||||
val pos = 1000L * position / duration
|
||||
binding.progressBar.progress = pos.toInt()
|
||||
}
|
||||
val percent = playerControl.bufferPercentage
|
||||
binding.progressBar.setSecondaryProgress(percent * 10)
|
||||
val endTime = if (duration > 0) formatTime(duration) else "--:--"
|
||||
binding.totalTimeText.text = endTime
|
||||
binding.currentTimeText.text = formatTime(position)
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
val keyCode = event.keyCode
|
||||
val uniqueDown = (event.repeatCount == 0 && event.action == KeyEvent.ACTION_DOWN)
|
||||
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_SPACE -> {
|
||||
if (uniqueDown) {
|
||||
doPauseResume()
|
||||
// show(sDefaultTimeout);
|
||||
binding.playBtn.requestFocus()
|
||||
}
|
||||
return true
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY -> {
|
||||
if (uniqueDown && playerControl?.isPlaying == false) {
|
||||
playerControl?.start()
|
||||
updatePausePlay()
|
||||
}
|
||||
return true
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_STOP,
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE
|
||||
-> {
|
||||
if (uniqueDown && playerControl?.isPlaying == true) {
|
||||
playerControl?.pause()
|
||||
updatePausePlay()
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> return super.dispatchKeyEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePausePlay() {
|
||||
binding.playBtn.icon = ContextCompat.getDrawable(
|
||||
context,
|
||||
if (playerControl?.isPlaying == true) {
|
||||
R.drawable.ic_pause
|
||||
} else { R.drawable.ic_play }
|
||||
)
|
||||
binding.forwardBtn.visibility = if (playerControl?.canSeekForward() == true) { VISIBLE } else { INVISIBLE }
|
||||
binding.rewindBtn.visibility = if (playerControl?.canSeekBackward() == true) { VISIBLE } else { INVISIBLE }
|
||||
}
|
||||
|
||||
private fun doPauseResume() {
|
||||
playerControl?.run {
|
||||
if (isPlaying) {
|
||||
pause()
|
||||
} else {
|
||||
start()
|
||||
}
|
||||
}
|
||||
updatePausePlay()
|
||||
}
|
||||
|
||||
override fun setEnabled(enabled: Boolean) {
|
||||
binding.playBtn.setEnabled(enabled)
|
||||
binding.forwardBtn.setEnabled(enabled)
|
||||
binding.rewindBtn.setEnabled(enabled)
|
||||
binding.progressBar.setEnabled(enabled)
|
||||
|
||||
disableUnsupportedButtons()
|
||||
|
||||
super.setEnabled(enabled)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onClick(v: View) {
|
||||
var pos: Int
|
||||
|
||||
playerControl?.let { playerControl ->
|
||||
val playing = playerControl.isPlaying
|
||||
val id = v.id
|
||||
|
||||
when (id) {
|
||||
R.id.playBtn -> {
|
||||
doPauseResume()
|
||||
}
|
||||
R.id.rewindBtn -> {
|
||||
pos = playerControl.currentPosition
|
||||
pos -= 5000
|
||||
playerControl.seekTo(pos)
|
||||
if (!playing) {
|
||||
playerControl.pause() // necessary in some 2.3.x devices
|
||||
}
|
||||
setProgress()
|
||||
}
|
||||
R.id.forwardBtn -> {
|
||||
pos = playerControl.currentPosition
|
||||
pos += 15000
|
||||
playerControl.seekTo(pos)
|
||||
|
||||
if (!playing) {
|
||||
playerControl.pause() // necessary in some 2.3.x devices
|
||||
}
|
||||
|
||||
setProgress()
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (!fromUser) {
|
||||
// We're not interested in programmatically generated changes to
|
||||
// the progress bar's position.
|
||||
return
|
||||
}
|
||||
|
||||
playerControl?.let { playerControl ->
|
||||
val duration = playerControl.duration.toLong()
|
||||
val newPosition = duration * progress / 1000L
|
||||
playerControl.seekTo(newPosition.toInt())
|
||||
binding.currentTimeText.text = formatTime(newPosition.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in devices with touchpad when the user starts to adjust the position of the seekbar's thumb.
|
||||
*
|
||||
* Will be followed by several onProgressChanged notifications.
|
||||
*/
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||
isDragging = true // monitors the duration of dragging
|
||||
handler.removeMessages(SHOW_PROGRESS) // grants no more updates with media player progress while dragging
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in devices with touchpad when the user finishes the adjusting of the seekbar.
|
||||
*/
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||
isDragging = false
|
||||
setProgress()
|
||||
updatePausePlay()
|
||||
handler.sendEmptyMessage(SHOW_PROGRESS) // grants future updates with media player progress
|
||||
}
|
||||
|
||||
override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
|
||||
super.onInitializeAccessibilityEvent(event)
|
||||
event.setClassName(MediaControlView::class.java.getName())
|
||||
}
|
||||
|
||||
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
|
||||
super.onInitializeAccessibilityNodeInfo(info)
|
||||
info.setClassName(MediaControlView::class.java.getName())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = MediaControlView::class.java.getSimpleName()
|
||||
private const val SHOW_PROGRESS = 1
|
||||
}
|
||||
}
|
15
app/src/main/res/drawable/ic_fast_forward.xml
Normal file
15
app/src/main/res/drawable/ic_fast_forward.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!--
|
||||
~ Nextcloud - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M100,720v-480l360,240 -360,240ZM500,720v-480l360,240 -360,240Z"/>
|
||||
</vector>
|
15
app/src/main/res/drawable/ic_fast_rewind.xml
Normal file
15
app/src/main/res/drawable/ic_fast_rewind.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!--
|
||||
~ Nextcloud - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M860,720 L500,480l360,-240v480ZM460,720L100,480l360,-240v480Z"/>
|
||||
</vector>
|
|
@ -9,7 +9,7 @@
|
|||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground_highlight"
|
||||
android:pathData="M560,760L560,200L720,200L720,760L560,760ZM240,760L240,200L400,200L400,760L240,760Z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M360,640h80v-320h-80v320ZM520,640h80v-320h-80v320ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z"/>
|
||||
</vector>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<!--
|
||||
~ Nextcloud - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
|
||||
~ SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
|
@ -11,6 +10,6 @@
|
|||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/foreground_highlight"
|
||||
android:pathData="M320,760v-560l440,280 -440,280Z"/>
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m380,660 l280,-180 -280,-180v360ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z"/>
|
||||
</vector>
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Nextcloud - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
~ SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
-->
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/grey_200">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?android:colorPrimary" />
|
||||
</shape>
|
||||
<color android:color="@color/white" />
|
||||
</item>
|
||||
</ripple>
|
|
@ -7,49 +7,76 @@
|
|||
~ SPDX-FileCopyrightText: 2013 David A. Velasco <dvelasco@solidgear.es>
|
||||
~ SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
android:id="@+id/media_control_linear_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_marginBottom="@dimen/standard_double_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="50dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="@dimen/standard_quarter_padding">
|
||||
|
||||
<ImageButton
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/rewindBtn"
|
||||
style="@android:style/MediaButton.Rew"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
app:backgroundTint="@color/transparent"
|
||||
app:iconTint="@color/black"
|
||||
app:iconGravity="top"
|
||||
android:layout_marginEnd="@dimen/standard_half_margin"
|
||||
android:paddingTop="@dimen/alternate_padding"
|
||||
app:icon="@drawable/ic_fast_rewind"
|
||||
android:contentDescription="@string/media_rewind_description" />
|
||||
|
||||
<ImageButton
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/playBtn"
|
||||
style="@android:style/MediaButton.Play"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
app:backgroundTint="@color/transparent"
|
||||
app:iconTint="@color/black"
|
||||
app:iconGravity="top"
|
||||
android:paddingTop="@dimen/alternate_padding"
|
||||
app:icon="@drawable/ic_play"
|
||||
android:layout_marginEnd="@dimen/standard_half_margin"
|
||||
|
||||
android:contentDescription="@string/media_play_pause_description" />
|
||||
|
||||
<ImageButton
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/forwardBtn"
|
||||
style="@android:style/MediaButton.Ffwd"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
app:backgroundTint="@color/transparent"
|
||||
app:iconTint="@color/black"
|
||||
app:iconGravity="top"
|
||||
android:paddingTop="@dimen/alternate_padding"
|
||||
app:icon="@drawable/ic_fast_forward"
|
||||
android:contentDescription="@string/media_forward_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="50dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currentTimeText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingStart="@dimen/standard_quarter_padding"
|
||||
android:paddingTop="@dimen/standard_quarter_padding"
|
||||
android:paddingEnd="@dimen/standard_quarter_padding"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/standard_quarter_padding"
|
||||
android:gravity="center"
|
||||
android:text="@string/placeholder_media_time"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/two_line_secondary_text_size"
|
||||
|
@ -59,7 +86,7 @@
|
|||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/seek_bar_height"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:splitTrack="false"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
@ -67,11 +94,9 @@
|
|||
<TextView
|
||||
android:id="@+id/totalTimeText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingStart="@dimen/standard_quarter_padding"
|
||||
android:paddingTop="@dimen/standard_quarter_padding"
|
||||
android:paddingEnd="@dimen/standard_quarter_padding"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/standard_quarter_padding"
|
||||
android:gravity="center"
|
||||
android:text="@string/placeholder_media_time"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/two_line_secondary_text_size"
|
||||
|
|
|
@ -53,7 +53,6 @@
|
|||
<color name="nc_grey">#ededed</color>
|
||||
<color name="icon_on_nc_grey">#000000</color>
|
||||
<color name="foreground_highlight">#1D1B1E</color>
|
||||
|
||||
<color name="process_dialog_background">#ffffff</color>
|
||||
<color name="indicator_dot_selected">#ffffff</color>
|
||||
<color name="drawer_shadow">#000000</color>
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
<dimen name="file_download_fragment_display_text_margin">40dp</dimen>
|
||||
<dimen name="drawer_width">240dp</dimen>
|
||||
<dimen name="grid_item_text_size">16sp</dimen>
|
||||
<dimen name="seek_bar_height">32dp</dimen>
|
||||
<dimen name="search_users_groups_layout_width">200dp</dimen>
|
||||
<dimen name="search_users_groups_layout_list_view_margin">20dp</dimen>
|
||||
<dimen name="share_file_layout_text_size">12sp</dimen>
|
||||
|
|
Loading…
Reference in a new issue